From 0c565e6b3113e0de6ec1fa8c987716298cc26cad Mon Sep 17 00:00:00 2001 From: Sean T Allen Date: Sat, 15 Oct 2016 22:54:46 -0400 Subject: [PATCH 1/9] Add unreleased section to CHANGELOG post 0.5.1 release prep --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74f5b230e2..7b94ee0be2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to the Pony compiler and standard library will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/) and [Keep a CHANGELOG](http://keepachangelog.com/). +## [unreleased] - unreleased + +### Fixed + +### Added + +### Changed + ## [0.5.1] - 2016-10-15 ### Fixed From 50faab2005e8c06c03044e9612d9fc393adf4b0f Mon Sep 17 00:00:00 2001 From: Theo Butler Date: Mon, 17 Oct 2016 15:16:43 -0400 Subject: [PATCH 2/9] run net/TCP.expect test on Windows (#1333) --- packages/net/_test.pony | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/net/_test.pony b/packages/net/_test.pony index ce26bd7db8..b9a5925022 100644 --- a/packages/net/_test.pony +++ b/packages/net/_test.pony @@ -7,9 +7,7 @@ actor Main is TestList fun tag tests(test: PonyTest) => test(_TestBroadcast) test(_TestTCPWritev) - ifdef not windows then - test(_TestTCPExpect) - end + test(_TestTCPExpect) class _TestPing is UDPNotify let _h: TestHelper From 59ca6bc0d4a24b1f2736c5e3448c1a9ccb0bfe4a Mon Sep 17 00:00:00 2001 From: "Jip J. Dekker" Date: Tue, 18 Oct 2016 17:40:54 +0200 Subject: [PATCH 3/9] Add DTrace/SystemTap support to Pony (#1324) In this contribution we have replaced the telemetry functionality of the compiler by scripts for both technologies. It shows the clean approach of DTrace/SystemTap. Support for Windows might be limited by this change. However since there was no support for the telemetry in the Windows build tools, we thought this would be the best approach. Currently implemented probes have a focus on replacing the events used by the telemetry functionality. We plan to add additional probes in the coming weeks, but we were hoping on possible feedback to change our approach if necessary. Contributors to this addition are: @mkdubik, @DarkLog1x, and me --- .gitignore | 1 + CHANGELOG.md | 4 + Makefile | 28 ++++++- examples/dtrace/README.md | 131 +++++++++++++++++++++++++++++ examples/dtrace/gc.d | 22 +++++ examples/dtrace/telemetry.d | 103 +++++++++++++++++++++++ examples/systemtap/README.md | 137 +++++++++++++++++++++++++++++++ examples/systemtap/gc.stp | 20 +++++ examples/systemtap/telemetry.stp | 109 ++++++++++++++++++++++++ src/common/dtrace.h | 35 ++++++++ src/common/dtrace_probes.d | 48 +++++++++++ src/libponyrt/actor/actor.c | 52 +++--------- src/libponyrt/gc/trace.c | 17 ++-- src/libponyrt/sched/scheduler.c | 48 ----------- src/libponyrt/sched/scheduler.h | 21 ----- 15 files changed, 649 insertions(+), 127 deletions(-) create mode 100644 examples/dtrace/README.md create mode 100755 examples/dtrace/gc.d create mode 100755 examples/dtrace/telemetry.d create mode 100644 examples/systemtap/README.md create mode 100644 examples/systemtap/gc.stp create mode 100644 examples/systemtap/telemetry.stp create mode 100644 src/common/dtrace.h create mode 100644 src/common/dtrace_probes.d diff --git a/.gitignore b/.gitignore index b07ffd5e74..b3d62b3d86 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ work/ output/ _ReSharper.* .vscode/ +src/common/dtrace_probes.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b94ee0be2..9fd2dfceea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,12 @@ All notable changes to the Pony compiler and standard library will be documented ### Added +- DTrace and SystemTap support - `use=dtrace` + ### Changed +- Replaces `use=telemetry` by DTrace/SystemTap scripts + ## [0.5.1] - 2016-10-15 ### Fixed diff --git a/Makefile b/Makefile index 83951fbbf5..143bec2060 100644 --- a/Makefile +++ b/Makefile @@ -124,9 +124,14 @@ ifdef use PONY_BUILD_DIR := $(PONY_BUILD_DIR)-pooltrack endif - ifneq (,$(filter $(use), telemetry)) - ALL_CFLAGS += -DUSE_TELEMETRY - PONY_BUILD_DIR := $(PONY_BUILD_DIR)-telemetry + ifneq (,$(filter $(use), dtrace)) + DTRACE ?= $(shell which dtrace) + ifeq (, $(DTRACE)) + $(error No dtrace compatible user application static probe generation tool found) + endif + + ALL_CFLAGS += -DUSE_DYNAMIC_TRACE + PONY_BUILD_DIR := $(PONY_BUILD_DIR)-dtrace endif endif @@ -371,6 +376,10 @@ ifeq ($(OSTYPE),linux) libponyrt.tests.links += pthread dl endif +ifneq (, $(DTRACE)) + $(shell $(DTRACE) -h -s $(PONY_SOURCE_DIR)/common/dtrace_probes.d -o $(PONY_SOURCE_DIR)/common/dtrace_probes.h) +endif + # Overwrite the default linker for a target. ponyc.linker = $(CXX) #compile as C but link as CPP (llvm) @@ -523,7 +532,17 @@ $(foreach d,$($(1).depends),$(eval depends += $($(d))/$(d).$(LIB_EXT))) ifeq ($(1),libponyrt) $($(1))/libponyrt.$(LIB_EXT): $(depends) $(ofiles) @echo 'Linking libponyrt' + ifneq (,$(DTRACE)) + ifeq ($(OSTYPE), linux) + @echo 'Generating dtrace object file' + $(SILENT)$(DTRACE) -G -s $(PONY_SOURCE_DIR)/common/dtrace_probes.d -o $(PONY_BUILD_DIR)/dtrace_probes.o + $(SILENT)$(AR) $(AR_FLAGS) $$@ $(ofiles) $(PONY_BUILD_DIR)/dtrace_probes.o + else $(SILENT)$(AR) $(AR_FLAGS) $$@ $(ofiles) + endif + else + $(SILENT)$(AR) $(AR_FLAGS) $$@ $(ofiles) + endif ifeq ($(runtime-bitcode),yes) $($(1))/libponyrt.bc: $(depends) $(bcfiles) @echo 'Generating bitcode for libponyrt' @@ -685,6 +704,7 @@ stats: clean: @rm -rf $(PONY_BUILD_DIR) + @rm src/common/dtrace_probes.h -@rmdir build 2>/dev/null ||: @echo 'Repository cleaned ($(PONY_BUILD_DIR)).' @@ -702,7 +722,7 @@ help: @echo 'USE OPTIONS:' @echo ' valgrind' @echo ' pooltrack' - @echo ' telemetry' + @echo ' dtrace' @echo @echo 'TARGETS:' @echo ' libponyc Pony compiler library' diff --git a/examples/dtrace/README.md b/examples/dtrace/README.md new file mode 100644 index 0000000000..cdbc3ef4a8 --- /dev/null +++ b/examples/dtrace/README.md @@ -0,0 +1,131 @@ +# DTrace in PonyC + +[DTrace](http://dtrace.org/guide/preface.html) provides a library for dynamic +tracing of events. This document describes the implementation of DTrace within +the Pony compiler. In particular, it focuses on how to use and extend the DTrace +implementation within Pony. + +DTrace is only available for MacOS and BSD. For Linux there is an alternative +called [SystemTap](https://sourceware.org/systemtap/). SystemTap uses the same +resources as DTrace, but uses different scripts. You will find more information +on using SystemTap within the Pony compiler [here](../systemtap/README.md). + +## Using DTrace scripts for Pony + +You can find various example DTrace scripts in `example/dtrace/`. To run these +scripts, you can use a command in the form of `[script] -c [binary]`. You need +to use a PonyC compiler compiled with the DTrace flag to use DTrace. To compile +a DTrace enabled compiler, you can use the following command: `make +config=release use=dtrace`. + +We can also execute a script using the dtrace command. This is necessary if the +script does not contain a "Shebang". We can do it using the following command: +`dtrace -s [script] -c [binary]`. + +### MacOS 10.11+ + +MacOS El Capitan for the first time included "System Integrity Protection". This +feature does not allow for all DTrace features to be used. This includes custom +DTrace providers. To get around this problem, the following steps can be +followed: + +1. Boot your Mac into Recovery Mode. +2. Open the Terminal from the Utilities menu. +3. Then use the following commands: + +```bash + +csrutil clear # restore the default configuration first + +csrutil enable --without dtrace # disable dtrace restrictions *only* + +``` + +After completing these steps your Mac should be able to use DTrace after a +restart. For more information visit the following article: +[link](http://internals.exposed/blog/dtrace-vs-sip.html). + +## Writing scripts for DTrace in Pony + +The following paragraph will inform you on the Pony provider and its probes for +DTrace. We refer to the [DTrace +documentation](http://dtrace.org/guide/preface.html) for more information on the +syntax and possibilities. + +The Pony provider for DTrace consists of DTrace probes implemented in the +Runtime. A binary compiled with DTrace enabled will allow the user access to the +information of the probes. You can find a list of all probes and their arguments +in [`src/common/dtrace_probes.d`](../../src/common/dtrace_probes.d). The +following is a toy example DTrace script: + +``` + +pony$target:::gc-start +{ + @ = count(); +} + +END +{ + printa(@); +} + +``` + +This script increases a counter every time the "GC Start" probe is *fired* and +prints it result at the end. Note the way in which we access the probe. It +consists of two parts: the first part is the provider and the second part, after `:::` +is the name of the probe. The provider is during runtime appended by the process +ID. In this example we use the `$target` variable to find the process ID. +Another option is to use `pony*`, but note that this could match many processes. +The name of the probe are also different from the names in +[`src/common/dtrace_probes.d`](../../src/common/dtrace_probes.d). In Dtrace +scripts you have to replace the `__` in the names by `-`. + +To make these scripts executable, like the ones in the examples, we use the +following "Shebang": `#!/usr/bin/env dtrace -s`. Extra options can be added to +improve its functionality. + +## Extending the DTrace implementation in Pony + +You can extend the DTrace implementation by adding more probes or extra +information to existing probes. All probes are defined in +[`src/common/dtrace_probes.d`](../../src/common/dtrace_probes.d). Usually their +names of their module and the event that triggers them. To install Probes in C +use the macros defined in `src/common/dtrace.h`. To fire a probe in the C code +use the macro `DTRACEx`; where `x` is the number of arguments of the probe. +There is also a macro `DTRACE_ENABLED`; its use allows for code to be only +executed when a probe is enabled. + +### Adding a probe + +The first step to add a probe is to add its definition into +[`src/common/dtrace_probes.d`](../../src/common/dtrace_probes.d). We have split +the names of the probes into two parts: its category and the specific event. The +name is split using `__`. We also add a comment to a probe explaining when it's +fired and the information contained in its arguments. + +After adding the probe, we use it in the C code using the macros. Note that the +split in the name is only a single underscore in the C code. The name of the +probe should also be capitalised. We can call the probe defined as `gc__start` +using the statement `DTRACE1(GC_START, scheduler)`. In this statement +`scheduler` is the data used as the first argument. + +Then once the probe has been placed in all the appropriate places, we are ready +to recompile. Make sure to use the DTrace flag while compiling. Recompiling will +create the appropriate files using the system installation of DTrace. + +### Extending a probe + +We can extend a probe by adding an extra argument to the probe. Like adding a +probe, the probe definition needs to be appended in +[`src/common/dtrace_probes.d`](../../src/common/dtrace_probes.d). Note that this +extra information needs to be available **everywhere** the probe is used. + +Once you've added the argument, you need to change **all** instances where the +probe is in use. Note that you have to both add the new argument and change the +`DTRACE` macro. Then you can recompile the PonyC compiler to use your new +arguments. + +*Do not forget that if you change the order of the arguments for any of the +existing nodes, you also need to change their usage in the example scripts.* diff --git a/examples/dtrace/gc.d b/examples/dtrace/gc.d new file mode 100755 index 0000000000..08c08b6619 --- /dev/null +++ b/examples/dtrace/gc.d @@ -0,0 +1,22 @@ +#!/usr/bin/env dtrace -s + +#pragma D option quiet + +pony$target:::gc-start +{ + @count["GC Passes"] = count(); + self->start_gc = timestamp; +} + +pony$target:::gc-end +{ + @quant["Time in GC (ns)"] = quantize(timestamp - self->start_gc); + @times["Total time"] = sum(timestamp - self->start_gc); +} + +END +{ + printa(@count); + printa(@quant); + printa(@times); +} diff --git a/examples/dtrace/telemetry.d b/examples/dtrace/telemetry.d new file mode 100755 index 0000000000..5251b4307a --- /dev/null +++ b/examples/dtrace/telemetry.d @@ -0,0 +1,103 @@ +#!/usr/bin/env dtrace -x aggsortkey -x aggsortkeypos=1 -s + +#pragma D option quiet + +inline unsigned int UINT32_MAX = -1; +inline unsigned int ACTORMSG_BLOCK = (UINT32_MAX - 6); +inline unsigned int ACTORMSG_UNBLOCK = (UINT32_MAX - 5); +inline unsigned int ACTORMSG_ACQUIRE = (UINT32_MAX - 4); +inline unsigned int ACTORMSG_RELEASE = (UINT32_MAX - 3); +inline unsigned int ACTORMSG_CONF = (UINT32_MAX - 2); +inline unsigned int ACTORMSG_ACK = (UINT32_MAX - 1); + +pony$target:::actor-msg-send +/ (unsigned int)arg1 == (unsigned int)ACTORMSG_BLOCK / +{ + @counts[arg0, "Block Messages Sent"] = count(); +} + +pony$target:::actor-msg-send +/ (unsigned int)arg1 == (unsigned int)ACTORMSG_UNBLOCK / +{ + @counts[arg0, "Unblock Messages Sent"] = count(); +} + +pony$target:::actor-msg-send +/ (unsigned int)arg1 == (unsigned int)ACTORMSG_ACQUIRE / +{ + @counts[arg0, "Acquire Messages Sent"] = count(); +} + +pony$target:::actor-msg-send +/ (unsigned int)arg1 == (unsigned int)ACTORMSG_RELEASE / +{ + @counts[arg0, "Release Messages Sent"] = count(); +} + +pony$target:::actor-msg-send +/ (unsigned int)arg1 == (unsigned int)ACTORMSG_CONF / +{ + @counts[arg0, "Confirmation Messages Sent"] = count(); +} + +pony$target:::actor-msg-send +/ (unsigned int)arg1 == (unsigned int)ACTORMSG_ACK / +{ + @counts[arg0, "Acknowledgement Messages Sent"] = count(); +} + +pony$target:::actor-msg-send +/ (unsigned int)arg1 < (unsigned int)ACTORMSG_BLOCK / +{ + @counts[arg0, "Application Messages Sent"] = count(); +} + +pony$target:::actor-alloc +{ + @counts[arg0, "Actor Allocations"] = count(); +} + +pony$target:::heap-alloc +{ + @counts[arg0, "Heap Allocations"] = count(); + @sizes[arg0, "Heap Allocations Size"] = sum(arg1); +} + +pony$target:::gc-start +{ + @counts[arg0, "GC Passes"] = count(); + self->start_gc = timestamp; +} + +pony$target:::gc-end +{ + @times[arg0, "Time in GC"] = sum(timestamp - self->start_gc); +} + +pony$target:::gc-send-start +{ + @counts[arg0, "Objects Sent"] = count(); + self->start_send = timestamp; +} + +pony$target:::gc-send-end +{ + @times[arg0, "Time in Send Scan"] = sum(timestamp - self->start_send); +} + +pony$target:::gc-recv-start +{ + @counts[arg0, "Objects Received"] = count(); + self->start_recv = timestamp; +} + +pony$target:::gc-recv-end +{ + @times[arg0, "Time in Recv Scan"] = sum(timestamp - self->start_recv); +} + +END +{ + printf("%?s %-40s %10s %10s %10s\n", "SCHEDULER", "EVENT", "COUNT", "TIME (ns)", "SIZE"); + printa("%?p %-40s %@10d %@10d %@10d\n", @counts, @times, @sizes); +} diff --git a/examples/systemtap/README.md b/examples/systemtap/README.md new file mode 100644 index 0000000000..d5fd5ec8a9 --- /dev/null +++ b/examples/systemtap/README.md @@ -0,0 +1,137 @@ +# SystemTap in PonyC + +SystemTap is the Linux alternative to DTrace. Although DTrace is available for +Linux the licensing and philosophical differences have caused Linux developers +to create an alternative tracing framework that is compatible with the GPLv2, +thus SystemTap was created. SystemTap and DTrace operate in a similar fashion +but since then the frameworks have been developed independently. Complete +SystemTap documentation can be found +[here](https://sourceware.org/systemtap/documentation.html) + +## Requirements + +* Linux Kernel with UPROBES or UTRACE. + + If your Linux kernel is version 3.5 or higher, it already includes UPROBES. To + verify that the current kernel supports UPROBES natively, run the following + command: + + ``` + # grep CONFIG_UPROBES /boot/config-`uname -r` + ``` + + If the kernel supports the probes this will be the output: + ``` + CONFIG_UPROBES=y + ``` + + If the kernel's version is prior to 3.5, SystemTap automatically builds the + UPROBES module. However, you also need the UTRACE kernel extensions required + by the SystemTap user-space probing to track various user-space events. This + can be verified with this command: + + ``` + # grep CONFIG_UTRACE /boot/config-`uname -r + ``` + If the < 3.5 kernel supports user tracing this will be the output: + + ``` + CONFIG_UTRACE=y + ``` + + Note: UTRACE does not exist on kernel versions > 3.5 and has been replaced by + UPROBES + +* SystemTap > 2.6 + + You need the `dtrace` commandline tool to generate header and object files + that are needed in the compilation and linking of PonyC compiler. This is + requred on Linux systems. At the time of writing this the version of + SystemTap that these probes have been implemented and tested with is 2.6, on + Debian 8 Stable. Later versions should work. Earlier versions might not work + and must be tested independently. In debian based systems, SystemTap can be + installed with apt-get + + ``` + $sudo apt-get install systemtap systemtap-runtime + ``` + +## Using SystemTap scripts for PonyC + +You can find various example SystemTap scripts in `example/systemtap/`. To run +these scripts, a sample command would be of the form: + +``` +# stap [script path] -c [binary path] +``` + +NB: stap must be run as root (or sudo) since it compiles the SystemTap script +into a loadable kernel module and loads it into the kernel when the stap script +is running. + +You need to use a PonyC compiler compiled with the DTrace flag to use SystemTap. +To compile a SystemTap enabled compiler, add the use=dtrace flag into the make +command: + +`make config=release use=dtrace`. + +## Writing scripts for SystemTap in PonyC + +SystemTap and DTrace use the same syntax for creating providers and thus we +direct to the [DTrace documentation](http://dtrace.org/guide/preface.html) for +more information on the syntax and possibilities. + +The PonyC provider for SystemTap consists of DTrace probes implemented in the +Runtime. A binary compiled with DTrace enabled will allow the user access to the +information of the probes, which work with SystemTap scripts. You can find a +list of all probes and their arguments in +[`src/common/dtrace_probes.d`](../../src/common/dtrace_probes.d). The following +is a toy example of a SystemTap script: + +``` +global gc_passes + +probe process.mark("gc-start") +{ + gc_passes <<< $arg1; +} + +probe end +{ + printf("Total number of GC passes: %d\n", @count(gc_passes)); +} + +``` + +This simple example will hook into the executable that the -c parameter provides +and searches for the probe named "gc-start". Once execution of the executable is +started, the script increases a counter every time the "gc-start" probe is +accessed and at the end of the run the results of the counter are printed + +In SystemTap you can use the DTrace syntax of "gc-start" but you may also call +them like they are in the +[`src/common/dtrace_probes.d`](../../src/common/dtrace_probes.d) file, +"gc__start". + +All available probes in an executable can be listed like this: + +``` +# stap -L 'process("").mark("*")' +``` + +This is useful to confirm that probes are ready to be hooked in. + +## Extending the SystemTap implementation in Pony + +You can extend the SytemTap implementation by adding more probes or extra +information to existing probes. All probes are defined in +[`src/common/dtrace_probes.d`](../../src/common/dtrace_probes.d). Usually their +names of their module and the event that triggers them. To install Probes in C +use the macros defined in `src/common/dtrace.h`. To fire a probe in the C code +use the macro `DTRACEx`; where `x` is the number of arguments of the probe. +There is also a macro `DTRACE_ENABLED`; its use allows for code to be only +executed when a probe is enabled. + +For adding and extending probes please refer to the DTrace +[`README`](../dtrace/README.md#adding-a-probe) file, since this process works +exactly the same for both systems. diff --git a/examples/systemtap/gc.stp b/examples/systemtap/gc.stp new file mode 100644 index 0000000000..4472c5f9ef --- /dev/null +++ b/examples/systemtap/gc.stp @@ -0,0 +1,20 @@ +global time_in_gc, tmp_ts + +probe process.mark("gc-start") +{ + tmp_ts[$arg1] = gettimeofday_ns(); +} + +probe process.mark("gc-end") +{ + delta = gettimeofday_ns() - tmp_ts[$arg1]; + time_in_gc <<< delta; + delete tmp_ts[$arg1]; +} + +probe end +{ + printf("Total GC passes: %d\n", @count(time_in_gc)); + print(@hist_log(time_in_gc)); + printf("Total time spent in GC (in nano seconds): %d\n", @sum(time_in_gc)); +} diff --git a/examples/systemtap/telemetry.stp b/examples/systemtap/telemetry.stp new file mode 100644 index 0000000000..b33c435161 --- /dev/null +++ b/examples/systemtap/telemetry.stp @@ -0,0 +1,109 @@ +global gc_passes, time_in_gc, messages, alloc +global tmp_ts /* construct to hold temporary timestamps */ + +probe process.mark("actor-msg-send") +{ + /* At the time of writing no way was found to find the maximum + size of unsigned integer in SystemTap. This is needed since + the message type is calculated from that value. UINT32_MAX + must be changed on different types of machines */ + UINT32_MAX = 4294967296; + ACTORMSG_BLOCK = (UINT32_MAX - 6); + ACTORMSG_UNBLOCK = (UINT32_MAX - 5); + ACTORMSG_ACQUIRE = (UINT32_MAX - 4); + ACTORMSG_RELEASE = (UINT32_MAX - 3); + ACTORMSG_CONF = (UINT32_MAX - 2); + ACTORMSG_ACK = (UINT32_MAX - 1); + + if ($arg2 < ACTORMSG_BLOCK) { + messages[$arg1, "count_msg_app"] ++; + } + if ($arg2 == ACTORMSG_BLOCK) { + messages[$arg1, "count_msg_block"] ++; + } + if ($arg2 == ACTORMSG_UNBLOCK) { + messages[$arg1, "count_msg_unblock"] ++; + } + if ($arg2 == ACTORMSG_ACQUIRE) { + messages[$arg1, "count_msg_acquire"] ++; + } + if ($arg2 == ACTORMSG_RELEASE) { + messages[$arg1, "count_msg_release"] ++; + } + if ($arg2 == ACTORMSG_CONF) { + messages[$arg1, "count_msg_conf"] ++; + } + if ($arg2 == ACTORMSG_ACK) { + messages[$arg1, "count_msg_ack"] ++; + } +} + +probe process.mark("gc-start") +{ + gc_passes[$arg1] ++; + tmp_ts[$arg1, "gc"] = gettimeofday_us(); +} + +probe process.mark("gc-end") +{ + time_in_gc[$arg1, "gc"] = gettimeofday_us() - tmp_ts[$arg1, "gc"]; + delete tmp_ts[$arg1, "gc"]; +} + +probe process.mark("gc-send-start") +{ + tmp_ts[$arg1, "gc_send"] = gettimeofday_us(); +} + +probe process.mark("gc-send-end") +{ + time_in_gc[$arg1, "gc_send"] = gettimeofday_us() - tmp_ts[$arg1, "gc_send"]; + delete tmp_ts[$arg1, "gc_send"]; +} + +probe process.mark("gc-recv-start") +{ + tmp_ts[$arg1, "gc_recv"] = gettimeofday_us(); +} + +probe process.mark("gc-recv-end") +{ + time_in_gc[$arg1, "gc_recv"] = gettimeofday_us() - tmp_ts[$arg1, "gc_recv"]; + delete tmp_ts[$arg1, "gc_recv"]; +} + +probe process.mark("heap-alloc") +{ + alloc[$arg1, "heap"] ++; + alloc[$arg1, "size"] = alloc[$arg1, "size"] + $arg2; +} + +probe process.mark("actor-alloc") +{ + alloc[$arg1, "actor"] ++; +} + +probe end +{ + printf("\"telemetry\" : [\n"); + foreach (scheduler in gc_passes) { + printf(" {\n"); + printf(" \"scheduler_id\": %d,\n", scheduler); + printf(" \"count_gc_passes\": %d,\n", gc_passes[scheduler]); + printf(" \"count_alloc\": %d,\n", alloc[scheduler, "heap"]); + printf(" \"count_alloc_size\": %d,\n", alloc[scheduler, "size"]); + printf(" \"count_alloc_actors\": %d,\n", alloc[scheduler, "actor"]); + printf(" \"count_msg_app\": %d,\n", messages[scheduler, "count_msg_app"]); + printf(" \"count_msg_block\": %d,\n", messages[scheduler, "count_msg_block"]); + printf(" \"count_msg_unblock\": %d,\n", messages[scheduler, "count_msg_unblock"]); + printf(" \"count_msg_acquire\": %d,\n", messages[scheduler, "count_msg_acquire"]); + printf(" \"count_msg_release\": %d,\n", messages[scheduler, "count_msg_release"]); + printf(" \"count_msg_conf\": %d,\n", messages[scheduler, "count_msg_conf"]); + printf(" \"count_msg_ack\": %d,\n", messages[scheduler, "count_msg_ack"]); + printf(" \"time_in_gc\": %d,\n", time_in_gc[scheduler, "gc"]); + printf(" \"time_in_send_scan\": %d,\n", time_in_gc[scheduler, "gc_send"]); + printf(" \"time_in_recv_scan\": %d,\n", time_in_gc[scheduler, "gc_recv"]); + printf(" },\n"); + } + printf("]\n"); +} diff --git a/src/common/dtrace.h b/src/common/dtrace.h new file mode 100644 index 0000000000..64a64921e7 --- /dev/null +++ b/src/common/dtrace.h @@ -0,0 +1,35 @@ +#ifndef DTRACE_H +#define DTRACE_H + +#if defined(USE_DYNAMIC_TRACE) + +#include "dtrace_probes.h" + +#define DTRACE_ENABLED(name) \ + PONY_##name##_enabled() +#define DTRACE0(name) \ + PONY_##name() +#define DTRACE1(name, a0) \ + PONY_##name(a0) +#define DTRACE2(name, a0, a1) \ + PONY_##name((a0), (a1)) +#define DTRACE3(name, a0, a1, a2) \ + PONY_##name((a0), (a1), (a2)) +#define DTRACE4(name, a0, a1, a2, a3) \ + PONY_##name((a0), (a1), (a2), (a3)) +#define DTRACE5(name, a0, a1, a2, a3, a4) \ + PONY_##name((a0), (a1), (a2), (a3), (a4)) + +#else + +#define DTRACE_ENABLED(name) 0 +#define DTRACE0(name) do {} while (0) +#define DTRACE1(name, a0) do {} while (0) +#define DTRACE2(name, a0, a1) do {} while (0) +#define DTRACE3(name, a0, a1, a2) do {} while (0) +#define DTRACE4(name, a0, a1, a2, a3) do {} while (0) +#define DTRACE5(name, a0, a1, a2, a3, a4) do {} while (0) + +#endif + +#endif diff --git a/src/common/dtrace_probes.d b/src/common/dtrace_probes.d new file mode 100644 index 0000000000..55194437ff --- /dev/null +++ b/src/common/dtrace_probes.d @@ -0,0 +1,48 @@ +provider pony { + /** + * Fired when a actor is being created + */ + probe actor__alloc(uintptr_t scheduler); + + /** + * Fired when a message is being send + * @param id the message id + */ + probe actor__msg__send(uintptr_t scheduler, uint32_t id); + + /** + * Fired when the garbage collection function is ending + */ + probe gc__end(uintptr_t scheduler); + + /** + * Fired when the garbage collector finishes sending an object + */ + probe gc__send__end(uintptr_t scheduler); + + /** + * Fired when the garbage collector stats sending an object + */ + probe gc__send__start(uintptr_t scheduler); + + /** + * Fired when the garbage collector finishes receiving an object + */ + probe gc__recv__end(uintptr_t scheduler); + + /** + * Fired when the garbage collector starts receiving an object + */ + probe gc__recv__start(uintptr_t scheduler); + + /** + * Fired when the garbage collection function has started + */ + probe gc__start(uintptr_t scheduler); + + /** + * Fired when memory is allocated on the heap + * @param size the size of the allocated memory + */ + probe heap__alloc(uintptr_t scheduler, unsigned long size); +}; diff --git a/src/libponyrt/actor/actor.c b/src/libponyrt/actor/actor.c index 6ff6675b3c..0a049a04f7 100644 --- a/src/libponyrt/actor/actor.c +++ b/src/libponyrt/actor/actor.c @@ -9,6 +9,7 @@ #include #include #include +#include enum { @@ -105,10 +106,7 @@ static void try_gc(pony_ctx_t* ctx, pony_actor_t* actor) if(!ponyint_heap_startgc(&actor->heap)) return; -#ifdef USE_TELEMETRY - ctx->count_gc_passes++; - size_t tsc = ponyint_cpu_tick(); -#endif + DTRACE1(GC_START, (uintptr_t)ctx->scheduler); ponyint_gc_mark(ctx); @@ -118,9 +116,7 @@ static void try_gc(pony_ctx_t* ctx, pony_actor_t* actor) ponyint_mark_done(ctx); ponyint_heap_endgc(&actor->heap); -#ifdef USE_TELEMETRY - ctx->time_in_gc += (ponyint_cpu_tick() - tsc); -#endif + DTRACE1(GC_END, (uintptr_t)ctx->scheduler); } bool ponyint_actor_run(pony_ctx_t* ctx, pony_actor_t* actor, size_t batch) @@ -279,9 +275,7 @@ pony_actor_t* pony_create(pony_ctx_t* ctx, pony_type_t* type) { assert(type != NULL); -#ifdef USE_TELEMETRY - ctx->count_alloc_actors++; -#endif + DTRACE1(ACTOR_ALLOC, (uintptr_t)ctx->scheduler); // allocate variable sized actors correctly pony_actor_t* actor = (pony_actor_t*)ponyint_pool_alloc_size(type->size); @@ -332,18 +326,7 @@ pony_msg_t* pony_alloc_msg_size(size_t size, uint32_t id) void pony_sendv(pony_ctx_t* ctx, pony_actor_t* to, pony_msg_t* m) { -#ifdef USE_TELEMETRY - switch(m->id) - { - case ACTORMSG_BLOCK: ctx->count_msg_block++; break; - case ACTORMSG_UNBLOCK: ctx->count_msg_unblock++; break; - case ACTORMSG_ACQUIRE: ctx->count_msg_acquire++; break; - case ACTORMSG_RELEASE: ctx->count_msg_release++; break; - case ACTORMSG_CONF: ctx->count_msg_conf++; break; - case ACTORMSG_ACK: ctx->count_msg_ack++; break; - default: ctx->count_msg_app++; - } -#endif + DTRACE2(ACTOR_MSG_SEND, (uintptr_t)ctx->scheduler, m->id); if(ponyint_messageq_push(&to->q, m)) { @@ -384,50 +367,35 @@ void pony_continuation(pony_actor_t* self, pony_msg_t* m) void* pony_alloc(pony_ctx_t* ctx, size_t size) { -#ifdef USE_TELEMETRY - ctx->count_alloc++; - ctx->count_alloc_size += size; -#endif + DTRACE2(HEAP_ALLOC, (uintptr_t)ctx->scheduler, size); return ponyint_heap_alloc(ctx->current, &ctx->current->heap, size); } void* pony_alloc_small(pony_ctx_t* ctx, uint32_t sizeclass) { -#ifdef USE_TELEMETRY - ctx->count_alloc++; - ctx->count_alloc_size += HEAP_MIN << sizeclass; -#endif + DTRACE2(HEAP_ALLOC, (uintptr_t)ctx->scheduler, HEAP_MIN << sizeclass); return ponyint_heap_alloc_small(ctx->current, &ctx->current->heap, sizeclass); } void* pony_alloc_large(pony_ctx_t* ctx, size_t size) { -#ifdef USE_TELEMETRY - ctx->count_alloc++; - ctx->count_alloc_size += size; -#endif + DTRACE2(HEAP_ALLOC, (uintptr_t)ctx->scheduler, size); return ponyint_heap_alloc_large(ctx->current, &ctx->current->heap, size); } void* pony_realloc(pony_ctx_t* ctx, void* p, size_t size) { -#ifdef USE_TELEMETRY - ctx->count_alloc++; - ctx->count_alloc_size += size; -#endif + DTRACE2(HEAP_ALLOC, (uintptr_t)ctx->scheduler, size); return ponyint_heap_realloc(ctx->current, &ctx->current->heap, p, size); } void* pony_alloc_final(pony_ctx_t* ctx, size_t size, pony_final_fn final) { -#ifdef USE_TELEMETRY - ctx->count_alloc++; - ctx->count_alloc_size += size; -#endif + DTRACE2(HEAP_ALLOC, (uintptr_t)ctx->scheduler, size); void* p = ponyint_heap_alloc(ctx->current, &ctx->current->heap, size); ponyint_gc_register_final(ctx, p, final); diff --git a/src/libponyrt/gc/trace.c b/src/libponyrt/gc/trace.c index 71dd6f5280..1836f72f83 100644 --- a/src/libponyrt/gc/trace.c +++ b/src/libponyrt/gc/trace.c @@ -4,6 +4,7 @@ #include "../sched/cpu.h" #include "../actor/actor.h" #include +#include void pony_gc_send(pony_ctx_t* ctx) { @@ -11,9 +12,7 @@ void pony_gc_send(pony_ctx_t* ctx) ctx->trace_object = ponyint_gc_sendobject; ctx->trace_actor = ponyint_gc_sendactor; -#ifdef USE_TELEMETRY - ctx->tsc = ponyint_cpu_tick(); -#endif + DTRACE1(GC_SEND_START, (uintptr_t)ctx->scheduler); } void pony_gc_recv(pony_ctx_t* ctx) @@ -22,9 +21,7 @@ void pony_gc_recv(pony_ctx_t* ctx) ctx->trace_object = ponyint_gc_recvobject; ctx->trace_actor = ponyint_gc_recvactor; -#ifdef USE_TELEMETRY - ctx->tsc = ponyint_cpu_tick(); -#endif + DTRACE1(GC_RECV_START, (uintptr_t)ctx->scheduler); } void ponyint_gc_mark(pony_ctx_t* ctx) @@ -54,9 +51,7 @@ void pony_send_done(pony_ctx_t* ctx) ponyint_gc_sendacquire(ctx); ponyint_gc_done(ponyint_actor_gc(ctx->current)); -#ifdef USE_TELEMETRY - ctx->time_in_send_scan += (ponyint_cpu_tick() - ctx->tsc); -#endif + DTRACE1(GC_SEND_END, (uintptr_t)ctx->scheduler); } void pony_recv_done(pony_ctx_t* ctx) @@ -64,9 +59,7 @@ void pony_recv_done(pony_ctx_t* ctx) ponyint_gc_handlestack(ctx); ponyint_gc_done(ponyint_actor_gc(ctx->current)); -#ifdef USE_TELEMETRY - ctx->time_in_recv_scan += (ponyint_cpu_tick() - ctx->tsc); -#endif + DTRACE1(GC_RECV_END, (uintptr_t)ctx->scheduler); } void ponyint_mark_done(pony_ctx_t* ctx) diff --git a/src/libponyrt/sched/scheduler.c b/src/libponyrt/sched/scheduler.c index 8bbb645086..ee79629f6b 100644 --- a/src/libponyrt/sched/scheduler.c +++ b/src/libponyrt/sched/scheduler.c @@ -331,61 +331,13 @@ static void ponyint_sched_shutdown() ponyint_cycle_terminate(&scheduler[0].ctx); -#ifdef USE_TELEMETRY - printf("\"telemetry\": [\n"); -#endif - for(uint32_t i = 0; i < scheduler_count; i++) { while(ponyint_messageq_pop(&scheduler[i].mq) != NULL); ponyint_messageq_destroy(&scheduler[i].mq); ponyint_mpmcq_destroy(&scheduler[i].q); - -#ifdef USE_TELEMETRY - pony_ctx_t* ctx = &scheduler[i].ctx; - - printf( - " {\n" - " \"count_gc_passes\": " __zu ",\n" - " \"count_alloc\": " __zu ",\n" - " \"count_alloc_size\": " __zu ",\n" - " \"count_alloc_actors\": " __zu ",\n" - " \"count_msg_app\": " __zu ",\n" - " \"count_msg_block\": " __zu ",\n" - " \"count_msg_unblock\": " __zu ",\n" - " \"count_msg_acquire\": " __zu ",\n" - " \"count_msg_release\": " __zu ",\n" - " \"count_msg_conf\": " __zu ",\n" - " \"count_msg_ack\": " __zu ",\n" - " \"time_in_gc\": " __zu ",\n" - " \"time_in_send_scan\": " __zu ",\n" - " \"time_in_recv_scan\": " __zu "\n" - " }", - ctx->count_gc_passes, - ctx->count_alloc, - ctx->count_alloc_size, - ctx->count_alloc_actors, - ctx->count_msg_app, - ctx->count_msg_block, - ctx->count_msg_unblock, - ctx->count_msg_acquire, - ctx->count_msg_release, - ctx->count_msg_conf, - ctx->count_msg_ack, - ctx->time_in_gc, - ctx->time_in_send_scan, - ctx->time_in_recv_scan - ); - - if(i < (scheduler_count - 1)) - printf(",\n"); -#endif } -#ifdef USE_TELEMETRY - printf("\n]\n"); -#endif - ponyint_pool_free_size(scheduler_count * sizeof(scheduler_t), scheduler); scheduler = NULL; scheduler_count = 0; diff --git a/src/libponyrt/sched/scheduler.h b/src/libponyrt/sched/scheduler.h index 4ff31bed35..7155f76ef9 100644 --- a/src/libponyrt/sched/scheduler.h +++ b/src/libponyrt/sched/scheduler.h @@ -30,27 +30,6 @@ typedef struct pony_ctx_t void* serialise_buffer; size_t serialise_size; ponyint_serialise_t serialise; - -#ifdef USE_TELEMETRY - size_t tsc; - - size_t count_gc_passes; - size_t count_alloc; - size_t count_alloc_size; - size_t count_alloc_actors; - - size_t count_msg_app; - size_t count_msg_block; - size_t count_msg_unblock; - size_t count_msg_acquire; - size_t count_msg_release; - size_t count_msg_conf; - size_t count_msg_ack; - - size_t time_in_gc; - size_t time_in_send_scan; - size_t time_in_recv_scan; -#endif } pony_ctx_t; struct scheduler_t From 8ae0ecd7dcc1fc483cd964509f4a91ad35419dc3 Mon Sep 17 00:00:00 2001 From: Joe Eli McIlvain Date: Tue, 18 Oct 2016 23:34:20 -0700 Subject: [PATCH 4/9] Fix typo in reference to __clang_major__ in atomics.h. (#1338) --- CHANGELOG.md | 2 ++ src/common/pony/detail/atomics.h | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fd2dfceea..0c0e36d6ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ All notable changes to the Pony compiler and standard library will be documented ### Fixed +- Compiling ponyrt with Clang versions >= 3.3, < 3.6. + ### Added - DTrace and SystemTap support - `use=dtrace` diff --git a/src/common/pony/detail/atomics.h b/src/common/pony/detail/atomics.h index 08b1318943..4f858fde2c 100644 --- a/src/common/pony/detail/atomics.h +++ b/src/common/pony/detail/atomics.h @@ -55,7 +55,7 @@ using std::atomic_fetch_sub_explicit; # include # endif # define PONY_ATOMIC(T) T _Atomic -# elif __clang_major >= 3 && __clang_minor__ >= 3 +# elif __clang_major__ >= 3 && __clang_minor__ >= 3 // Valid on x86 and ARM given our uses of atomics. # define PONY_ATOMIC(T) T # define PONY_ATOMIC_BUILTINS From 85390453117a57bcce036c03b74a7118ab12f544 Mon Sep 17 00:00:00 2001 From: Malthe Borch Date: Thu, 13 Oct 2016 22:32:46 +0200 Subject: [PATCH 5/9] Use 'cpointer' instead of 'cstring' where applicable --- CHANGELOG.md | 4 ++ packages/assert/assert.pony | 2 +- packages/builtin/array.pony | 15 +++-- packages/builtin/std_stream.pony | 4 +- packages/builtin/stdin.pony | 2 +- packages/builtin/string.pony | 95 ++++++++++++++++++--------- packages/builtin_test/_test.pony | 2 +- packages/collections/hashable.pony | 4 +- packages/crypto/digest.pony | 6 +- packages/crypto/hash_fn.pony | 32 ++++----- packages/debug/debug.pony | 2 +- packages/files/directory.pony | 28 ++++---- packages/files/file.pony | 12 ++-- packages/files/file_info.pony | 6 +- packages/files/file_path.pony | 42 ++++++------ packages/files/path.pony | 2 +- packages/net/dns.pony | 6 +- packages/net/ssl/ssl.pony | 12 ++-- packages/net/ssl/ssl_context.pony | 6 +- packages/net/tcp_connection.pony | 22 +++---- packages/net/tcp_listener.pony | 6 +- packages/net/udp_socket.pony | 22 +++---- packages/process/process_monitor.pony | 12 ++-- packages/regex/match.pony | 4 +- packages/regex/regex.pony | 12 ++-- packages/time/date.pony | 3 +- 26 files changed, 200 insertions(+), 163 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c0e36d6ce..8b4ed1d7d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ All notable changes to the Pony compiler and standard library will be documented ### Changed - Replaces `use=telemetry` by DTrace/SystemTap scripts +- `String.cstring()` now always returns a null-terminated string + (which may result in a copy) while `cpointer()` (also available on + `Array` objects) returns a pointer to the underlying array as-is + (issue #1309). ## [0.5.1] - 2016-10-15 diff --git a/packages/assert/assert.pony b/packages/assert/assert.pony index 683b61a19f..7d9ce29733 100644 --- a/packages/assert/assert.pony +++ b/packages/assert/assert.pony @@ -25,7 +25,7 @@ primitive Fact if not test then if msg.size() > 0 then @fprintf[I32](@pony_os_stderr[Pointer[U8]](), "%s\n".cstring(), - msg.null_terminated().cstring()) + msg.cstring()) end error end diff --git a/packages/builtin/array.pony b/packages/builtin/array.pony index df12178f40..e877c009a5 100644 --- a/packages/builtin/array.pony +++ b/packages/builtin/array.pony @@ -41,7 +41,7 @@ class Array[A] is Seq[A] _ptr = Pointer[A] end - new from_cstring(ptr: Pointer[A], len: USize, alloc: USize = 0) => + new from_cpointer(ptr: Pointer[A], len: USize, alloc: USize = 0) => """ Create an array from a C-style pointer and length. The contents are not copied. @@ -56,15 +56,16 @@ class Array[A] is Seq[A] _ptr = ptr - fun cstring(): Pointer[A] tag => + fun _copy_to(ptr: Pointer[this->A!], copy_len: USize, + from_offset: USize = 0, to_offset: USize = 0) => """ - Return the underlying C-style pointer. + Copy copy_len elements from this to that at specified offsets. """ - _ptr + _ptr._offset(from_offset)._copy_to(ptr._offset(to_offset), copy_len) - fun _cstring(): Pointer[A] box => + fun cpointer(): Pointer[A] tag => """ - Internal cstring. + Return the underlying C-style pointer. """ _ptr @@ -208,7 +209,7 @@ class Array[A] is Seq[A] let alloc = _alloc - offset if size' > 0 then - from_cstring(_ptr._offset(offset)._unsafe(), size', alloc) + from_cpointer(_ptr._offset(offset)._unsafe(), size', alloc) else create() end diff --git a/packages/builtin/std_stream.pony b/packages/builtin/std_stream.pony index 306e9877be..7916b4f4f5 100644 --- a/packages/builtin/std_stream.pony +++ b/packages/builtin/std_stream.pony @@ -81,10 +81,10 @@ actor StdStream """ Write the bytes without explicitly flushing. """ - @pony_os_std_write[None](_stream, data.cstring(), data.size()) + @pony_os_std_write[None](_stream, data.cpointer(), data.size()) fun ref _print(data: ByteSeq) => """ Write the bytes and a newline without explicitly flushing. """ - @pony_os_std_print[None](_stream, data.cstring(), data.size()) + @pony_os_std_print[None](_stream, data.cpointer(), data.size()) diff --git a/packages/builtin/stdin.pony b/packages/builtin/stdin.pony index 6d01508194..8db1570a32 100644 --- a/packages/builtin/stdin.pony +++ b/packages/builtin/stdin.pony @@ -116,7 +116,7 @@ actor Stdin var data = recover Array[U8].undefined(chunk_size) end var again: Bool = false - let len = @pony_os_stdin_read[USize](data.cstring(), data.space(), + let len = @pony_os_stdin_read[USize](data.cpointer(), data.space(), addressof again) match len diff --git a/packages/builtin/string.pony b/packages/builtin/string.pony index 364aabfdfe..5539236b9e 100644 --- a/packages/builtin/string.pony +++ b/packages/builtin/string.pony @@ -70,11 +70,11 @@ actor Main try (data(_size - 1) == 0) else false end then _alloc = data.space() - _ptr = data._cstring()._unsafe() + _ptr = data.cpointer()._unsafe() else _alloc = _size + 1 _ptr = Pointer[U8]._alloc(_alloc) - data._cstring()._copy_to(_ptr, _size) + data._copy_to(_ptr, _size) _set(_size, 0) end @@ -84,15 +84,14 @@ actor Main """ _size = data.size() _alloc = data.space() - _ptr = (consume data)._cstring()._unsafe() + _ptr = (consume data).cpointer()._unsafe() - new from_cstring(str: Pointer[U8], len: USize = 0, alloc: USize = 0) => + new from_cpointer(str: Pointer[U8], len: USize, alloc: USize = 0) => """ - The cstring is not copied. This must be done only with C-FFI functions that - return pony_alloc'd character arrays. If len is not given, the pointer is - scanned for the first null byte, which will be interpreted as the null - terminator. Therefore, strings without null terminators must specify - a length to retain memory safety. + Return a string from binary pointer data without making a + copy. This must be done only with C-FFI functions that return + pony_alloc'd character arrays. If a null pointer is given then an + empty string is returned. """ if str.is_null() then _size = 0 @@ -101,14 +100,38 @@ actor Main _set(0, 0) else _size = len + _alloc = alloc.max(_size.min(len.max_value() - 1) + 1) + _ptr = str + end - if len == 0 then - while str._apply(_size) != 0 do - _size = _size + 1 + new from_cstring(str: Pointer[U8]) => + """ + Return a string from a pointer to a null-terminated cstring + without making a copy. The data is not copied. This must be done + only with C-FFI functions that return pony_alloc'd character + arrays. The pointer is scanned for the first null byte, which will + be interpreted as the null terminator. Note that the scan is + unbounded; the pointed to data must be null-terminated within + the allocated array to preserve memory safety. If a null pointer + is given then an empty string is returned. + """ + if str.is_null() then + _size = 0 + _alloc = 1 + _ptr = Pointer[U8]._alloc(_alloc) + _set(0, 0) + else + var i: USize = 0 + + while true do + if str._apply(i) == 0 then + break end + i = i + 1 end - _alloc = alloc.max(_size + 1) + _size = i + _alloc = i + 1 _ptr = str end @@ -177,24 +200,42 @@ actor Main end _set(_size, 0) - fun cstring(): Pointer[U8] tag => + fun box _copy_to(ptr: Pointer[U8] ref, copy_len: USize, + from_offset: USize = 0, to_offset: USize = 0) => """ - Returns a C compatible pointer to a null terminated string. + Copy copy_len characters from this to that at specified offsets. """ - _ptr + _ptr._offset(from_offset)._copy_to(ptr._offset(to_offset), copy_len) - fun _cstring(): Pointer[U8] box => + fun cpointer(): Pointer[U8] tag => """ - Returns a C compatible pointer to a null terminated string. + Returns a C compatible pointer to the underlying string allocation. """ _ptr + fun cstring(): Pointer[U8] tag => + """ + Returns a C compatible pointer to a null-terminated version of the + string, safe to pass to an FFI function that doesn't accept a size + argument, expecting a null-terminator. If the underlying string + is already null terminated, this is returned; otherwise the string + is copied into a new, null-terminated allocation. + """ + if is_null_terminated() then + return _ptr + end + + let ptr = Pointer[U8]._alloc(_size + 1) + _ptr._copy_to(ptr._unsafe(), _size) + ptr._update(_size + 1, 0) + ptr + fun val array(): Array[U8] val => """ Returns an Array[U8] that that reuses the underlying data pointer. """ recover - Array[U8].from_cstring(_ptr._unsafe(), _size, _alloc) + Array[U8].from_cpointer(_ptr._unsafe(), _size, _alloc) end fun size(): USize => @@ -329,7 +370,7 @@ actor Main let alloc = _alloc - offset if size' > 0 then - from_cstring(_ptr._offset(offset)._unsafe(), size', alloc) + from_cpointer(_ptr._offset(offset)._unsafe(), size', alloc) else create() end @@ -346,15 +387,6 @@ actor Main """ (_alloc > 0) and (_ptr._apply(_size) == 0) - fun null_terminated(): this->String ref => - """ - Returns a null-terminated version of the string, safe to pass to an FFI - function that doesn't accept a size argument, expecting a null-terminator. - If the underlying string is already null terminated, this is returned; - otherwise the string is cloned and the null-terminated clone is returned. - """ - if is_null_terminated() then this else clone() end - fun utf32(offset: ISize): (U32, U8) ? => """ Return a UTF32 representation of the character at the given offset and the @@ -473,7 +505,8 @@ actor Main fun clone(): String iso^ => """ - Returns a copy of the string. + Returns a copy of the string. The resulting string is + null-terminated even if the original is not. """ let len = _size let str = recover String(len) end @@ -781,7 +814,7 @@ actor Main match seq | let s: (String box | Array[U8] box) => - s._cstring()._offset(offset)._copy_to(_ptr._offset(_size), copy_len) + s._copy_to(_ptr, copy_len, offset, _size) _size = _size + copy_len _set(_size, 0) else diff --git a/packages/builtin_test/_test.pony b/packages/builtin_test/_test.pony index 72b9716b43..e3b253ebd7 100644 --- a/packages/builtin_test/_test.pony +++ b/packages/builtin_test/_test.pony @@ -433,7 +433,7 @@ class iso _TestStringIsNullTerminated is UnitTest h.assert_true("0123456".trim(4, 7).is_null_terminated()) h.assert_false("0123456".trim(2, 4).is_null_terminated()) h.assert_true("0123456".trim(2, 4).clone().is_null_terminated()) - h.assert_true("0123456".trim(2, 4).null_terminated().is_null_terminated()) + h.assert_false("0123456".trim(2, 4).is_null_terminated()) class iso _TestSpecialValuesF32 is UnitTest diff --git a/packages/collections/hashable.pony b/packages/collections/hashable.pony index ac417d3eb1..da40ad1d37 100644 --- a/packages/collections/hashable.pony +++ b/packages/collections/hashable.pony @@ -57,11 +57,11 @@ primitive HashByteSeq Hash and equality functions for arbitrary ByteSeq. """ fun hash(x: ByteSeq box): U64 => - @ponyint_hash_block[U64](x.cstring(), x.size()) + @ponyint_hash_block[U64](x.cpointer(), x.size()) fun eq(x: ByteSeq box, y: ByteSeq box): Bool => if x.size() == y.size() then - @memcmp[I32](x.cstring(), y.cstring(), x.size()) == 0 + @memcmp[I32](x.cpointer(), y.cpointer(), x.size()) == 0 else false end diff --git a/packages/crypto/digest.pony b/packages/crypto/digest.pony index 70fa379369..e9b402958c 100644 --- a/packages/crypto/digest.pony +++ b/packages/crypto/digest.pony @@ -76,7 +76,7 @@ class Digest called. """ if _hash isnt None then error end - @EVP_DigestUpdate[None](_ctx, input.cstring(), input.size()) + @EVP_DigestUpdate[None](_ctx, input.cpointer(), input.size()) fun ref final(): Array[U8] val => """ @@ -86,10 +86,10 @@ class Digest | let h: Array[U8] val => h else let size = _digest_size - let digest = recover String.from_cstring( + let digest = recover String.from_cpointer( @pony_alloc[Pointer[U8]](@pony_ctx[Pointer[None] iso](), size), size ) end - @EVP_DigestFinal_ex[None](_ctx, digest.cstring(), Pointer[USize]) + @EVP_DigestFinal_ex[None](_ctx, digest.cpointer(), Pointer[USize]) @EVP_MD_CTX_cleanup[None](_ctx) let h = (consume digest).array() _hash = h diff --git a/packages/crypto/hash_fn.pony b/packages/crypto/hash_fn.pony index 2abe94fe65..a0c0c592ac 100644 --- a/packages/crypto/hash_fn.pony +++ b/packages/crypto/hash_fn.pony @@ -21,8 +21,8 @@ primitive MD4 is HashFn @pony_ctx[Pointer[None] iso](), size ) - @MD4[Pointer[U8]](input.cstring(), input.size(), digest) - Array[U8].from_cstring(digest, size) + @MD4[Pointer[U8]](input.cpointer(), input.size(), digest) + Array[U8].from_cpointer(digest, size) end primitive MD5 is HashFn @@ -36,8 +36,8 @@ primitive MD5 is HashFn @pony_ctx[Pointer[None] iso](), size ) - @MD5[Pointer[U8]](input.cstring(), input.size(), digest) - Array[U8].from_cstring(digest, size) + @MD5[Pointer[U8]](input.cpointer(), input.size(), digest) + Array[U8].from_cpointer(digest, size) end primitive RIPEMD160 is HashFn @@ -51,8 +51,8 @@ primitive RIPEMD160 is HashFn @pony_ctx[Pointer[None] iso](), size ) - @RIPEMD160[Pointer[U8]](input.cstring(), input.size(), digest) - Array[U8].from_cstring(digest, size) + @RIPEMD160[Pointer[U8]](input.cpointer(), input.size(), digest) + Array[U8].from_cpointer(digest, size) end primitive SHA1 is HashFn @@ -67,8 +67,8 @@ primitive SHA1 is HashFn @pony_ctx[Pointer[None] iso](), size ) - @SHA1[Pointer[U8]](input.cstring(), input.size(), digest) - Array[U8].from_cstring(digest, size) + @SHA1[Pointer[U8]](input.cpointer(), input.size(), digest) + Array[U8].from_cpointer(digest, size) end primitive SHA224 is HashFn @@ -83,8 +83,8 @@ primitive SHA224 is HashFn @pony_ctx[Pointer[None] iso](), size ) - @SHA224[Pointer[U8]](input.cstring(), input.size(), digest) - Array[U8].from_cstring(digest, size) + @SHA224[Pointer[U8]](input.cpointer(), input.size(), digest) + Array[U8].from_cpointer(digest, size) end primitive SHA256 is HashFn @@ -99,8 +99,8 @@ primitive SHA256 is HashFn @pony_ctx[Pointer[None] iso](), size ) - @SHA256[Pointer[U8]](input.cstring(), input.size(), digest) - Array[U8].from_cstring(digest, size) + @SHA256[Pointer[U8]](input.cpointer(), input.size(), digest) + Array[U8].from_cpointer(digest, size) end primitive SHA384 is HashFn @@ -115,8 +115,8 @@ primitive SHA384 is HashFn @pony_ctx[Pointer[None] iso](), size ) - @SHA384[Pointer[U8]](input.cstring(), input.size(), digest) - Array[U8].from_cstring(digest, size) + @SHA384[Pointer[U8]](input.cpointer(), input.size(), digest) + Array[U8].from_cpointer(digest, size) end primitive SHA512 is HashFn @@ -131,8 +131,8 @@ primitive SHA512 is HashFn @pony_ctx[Pointer[None] iso](), size ) - @SHA512[Pointer[U8]](input.cstring(), input.size(), digest) - Array[U8].from_cstring(digest, size) + @SHA512[Pointer[U8]](input.cpointer(), input.size(), digest) + Array[U8].from_cpointer(digest, size) end primitive ToHexString diff --git a/packages/debug/debug.pony b/packages/debug/debug.pony index c11ab7bd33..0a6e5f38ad 100644 --- a/packages/debug/debug.pony +++ b/packages/debug/debug.pony @@ -61,7 +61,7 @@ primitive Debug ifdef debug then try @fprintf[I32](_stream(stream), "%s\n".cstring(), - msg.null_terminated().cstring()) + msg.cstring()) end end diff --git a/packages/files/directory.pony b/packages/files/directory.pony index d4ae7a47f2..d11ac344de 100644 --- a/packages/files/directory.pony +++ b/packages/files/directory.pony @@ -45,7 +45,7 @@ class Directory path = from ifdef posix then - _fd = @open[I32](from.path.null_terminated().cstring(), + _fd = @open[I32](from.path.cstring(), @ponyint_o_rdonly() or @ponyint_o_directory() or @ponyint_o_cloexec()) if _fd == -1 then @@ -94,7 +94,7 @@ class Directory @ponyint_o_cloexec()) @fdopendir[Pointer[_DirectoryHandle]](fd) else - @opendir[Pointer[_DirectoryHandle]](path'.null_terminated().cstring()) + @opendir[Pointer[_DirectoryHandle]](path'.cstring()) end if h.is_null() then @@ -147,7 +147,7 @@ class Directory let path' = FilePath(path, target, path.caps) ifdef linux or freebsd then - let fd' = @openat[I32](_fd, target.null_terminated().cstring(), + let fd' = @openat[I32](_fd, target.cstring(), @ponyint_o_rdonly() or @ponyint_o_directory() or @ponyint_o_cloexec()) _relative(path', fd') else @@ -210,7 +210,7 @@ class Directory let path' = FilePath(path, target, path.caps) ifdef linux or freebsd then - let fd' = @openat[I32](_fd, target.null_terminated().cstring(), + let fd' = @openat[I32](_fd, target.cstring(), @ponyint_o_rdwr() or @ponyint_o_creat() or @ponyint_o_cloexec(), I32(0x1B6)) recover File._descriptor(fd', path') end @@ -232,7 +232,7 @@ class Directory let path' = FilePath(path, target, path.caps - FileWrite) ifdef linux or freebsd then - let fd' = @openat[I32](_fd, target.null_terminated().cstring(), + let fd' = @openat[I32](_fd, target.cstring(), @ponyint_o_rdonly() or @ponyint_o_cloexec(), I32(0x1B6)) recover File._descriptor(fd', path') end else @@ -307,7 +307,7 @@ class Directory let path' = FilePath(path, target, path.caps) ifdef linux or freebsd then - 0 == @fchmodat[I32](_fd, target.null_terminated().cstring(), mode._os(), + 0 == @fchmodat[I32](_fd, target.cstring(), mode._os(), I32(0)) else path'.chmod(mode) @@ -332,7 +332,7 @@ class Directory let path' = FilePath(path, target, path.caps) ifdef linux or freebsd then - 0 == @fchownat[I32](_fd, target.null_terminated().cstring(), uid, gid, + 0 == @fchownat[I32](_fd, target.cstring(), uid, gid, I32(0)) else path'.chown(uid, gid) @@ -368,7 +368,7 @@ class Directory var tv: (ILong, ILong, ILong, ILong) = (atime._1.ilong(), atime._2.ilong() / 1000, mtime._1.ilong(), mtime._2.ilong() / 1000) - 0 == @futimesat[I32](_fd, target.null_terminated().cstring(), + 0 == @futimesat[I32](_fd, target.cstring(), addressof tv) else path'.set_time(atime, mtime) @@ -396,8 +396,8 @@ class Directory let path' = FilePath(path, link_name, path.caps) ifdef linux or freebsd then - 0 == @symlinkat[I32](source.path.null_terminated().cstring(), _fd, - link_name.null_terminated().cstring()) + 0 == @symlinkat[I32](source.path.cstring(), _fd, + link_name.cstring()) else source.symlink(path') end @@ -433,10 +433,10 @@ class Directory end end - 0 == @unlinkat(_fd, target.null_terminated().cstring(), + 0 == @unlinkat(_fd, target.cstring(), @ponyint_at_removedir()) else - 0 == @unlinkat(_fd, target.null_terminated().cstring(), 0) + 0 == @unlinkat(_fd, target.cstring(), 0) end else path'.remove() @@ -465,8 +465,8 @@ class Directory let path'' = FilePath(to.path, target, to.path.caps) ifdef linux or freebsd then - 0 == @renameat[I32](_fd, source.null_terminated().cstring(), to._fd, - target.null_terminated().cstring()) + 0 == @renameat[I32](_fd, source.cstring(), to._fd, + target.cstring()) else path'.rename(path'') end diff --git a/packages/files/file.pony b/packages/files/file.pony index 452a9032ad..750afcae4f 100644 --- a/packages/files/file.pony +++ b/packages/files/file.pony @@ -92,7 +92,7 @@ class File if mode != "x" then _handle = @fopen[Pointer[_FileHandle]]( - path.path.null_terminated().cstring(), mode.cstring()) + path.path.cstring(), mode.cstring()) if _handle.is_null() then _errno = _get_error() @@ -130,7 +130,7 @@ class File _errno = FileError else _handle = @fopen[Pointer[_FileHandle]]( - from.path.null_terminated().cstring(), "rb".cstring()) + from.path.cstring(), "rb".cstring()) if _handle.is_null() then _errno = FileError @@ -298,9 +298,9 @@ class File let result = recover Array[U8].undefined(len) end let r = ifdef linux then - @fread_unlocked[USize](result.cstring(), USize(1), len, _handle) + @fread_unlocked[USize](result.cpointer(), USize(1), len, _handle) else - @fread[USize](result.cstring(), USize(1), len, _handle) + @fread[USize](result.cpointer(), USize(1), len, _handle) end if r < len then @@ -362,9 +362,9 @@ class File """ if writeable and (not _handle.is_null()) then let len = ifdef linux then - @fwrite_unlocked[USize](data.cstring(), USize(1), data.size(), _handle) + @fwrite_unlocked[USize](data.cpointer(), USize(1), data.size(), _handle) else - @fwrite[USize](data.cstring(), USize(1), data.size(), _handle) + @fwrite[USize](data.cpointer(), USize(1), data.size(), _handle) end if len == data.size() then diff --git a/packages/files/file_info.pony b/packages/files/file_info.pony index af125a53ce..b5b96b3552 100644 --- a/packages/files/file_info.pony +++ b/packages/files/file_info.pony @@ -38,7 +38,7 @@ class val FileInfo filepath = from - if not @pony_os_stat[Bool](from.path.null_terminated().cstring(), this) then + if not @pony_os_stat[Bool](from.path.cstring(), this) then error end @@ -54,7 +54,7 @@ class val FileInfo filepath = path let fstat = - @pony_os_fstat[Bool](fd, path.path.null_terminated().cstring(), this) + @pony_os_fstat[Bool](fd, path.path.cstring(), this) if not fstat then error end new val _relative(fd: I32, path: FilePath, from: String) ? => @@ -65,5 +65,5 @@ class val FileInfo filepath = path let fstatat = - @pony_os_fstatat[Bool](fd, from.null_terminated().cstring(), this) + @pony_os_fstatat[Bool](fd, from.cstring(), this) if not fstatat then error end diff --git a/packages/files/file_path.pony b/packages/files/file_path.pony index 0ec63ede8f..194f143bf9 100644 --- a/packages/files/file_path.pony +++ b/packages/files/file_path.pony @@ -37,14 +37,14 @@ class val FilePath error end - path = Path.join(b.path, path').null_terminated() + path = Path.join(b.path, path') caps.intersect(b.caps) if not path.at(b.path, 0) then error end | let b: AmbientAuth => - path = Path.abs(path').null_terminated() + path = Path.abs(path') else error end @@ -79,13 +79,13 @@ class val FilePath caps.union(caps') caps.intersect(temp.caps) - path = temp.path.null_terminated() + path = temp.path new val _create(path': String, caps': FileCaps val) => """ Internal constructor. """ - path = path'.null_terminated() + path = path' caps.union(caps') fun val join(path': String, @@ -158,9 +158,9 @@ class val FilePath if element.size() > 0 then let r = ifdef windows then - @_mkdir[I32](element.null_terminated().cstring()) + @_mkdir[I32](element.cstring()) else - @mkdir[I32](element.null_terminated().cstring(), U32(0x1FF)) + @mkdir[I32](element.cstring(), U32(0x1FF)) end if r != 0 then @@ -205,15 +205,15 @@ class val FilePath ifdef windows then if info.directory and not info.symlink then - 0 == @_rmdir[I32](path.null_terminated().cstring()) + 0 == @_rmdir[I32](path.cstring()) else - 0 == @_unlink[I32](path.null_terminated().cstring()) + 0 == @_unlink[I32](path.cstring()) end else if info.directory and not info.symlink then - 0 == @rmdir[I32](path.null_terminated().cstring()) + 0 == @rmdir[I32](path.cstring()) else - 0 == @unlink[I32](path.null_terminated().cstring()) + 0 == @unlink[I32](path.cstring()) end end else @@ -228,8 +228,8 @@ class val FilePath return false end - 0 == @rename[I32](path.null_terminated().cstring(), - new_path.path.null_terminated().cstring()) + 0 == @rename[I32](path.cstring(), + new_path.path.cstring()) fun symlink(link_name: FilePath): Bool => """ @@ -240,11 +240,11 @@ class val FilePath end ifdef windows then - 0 != @CreateSymbolicLink[U8](link_name.path.null_terminated().cstring(), - path.null_terminated().cstring()) + 0 != @CreateSymbolicLink[U8](link_name.path.cstring(), + path.cstring()) else - 0 == @symlink[I32](path.null_terminated().cstring(), - link_name.path.null_terminated().cstring()) + 0 == @symlink[I32](path.cstring(), + link_name.path.cstring()) end fun chmod(mode: FileMode box): Bool => @@ -258,9 +258,9 @@ class val FilePath let m = mode._os() ifdef windows then - 0 == @_chmod[I32](path.null_terminated().cstring(), m) + 0 == @_chmod[I32](path.cstring(), m) else - 0 == @chmod[I32](path.null_terminated().cstring(), m) + 0 == @chmod[I32](path.cstring(), m) end fun chown(uid: U32, gid: U32): Bool => @@ -271,7 +271,7 @@ class val FilePath false else if caps(FileChown) then - 0 == @chown[I32](path.null_terminated().cstring(), uid, gid) + 0 == @chown[I32](path.cstring(), uid, gid) else false end @@ -293,10 +293,10 @@ class val FilePath ifdef windows then var tv: (I64, I64) = (atime._1, mtime._1) - 0 == @_utime64[I32](path.null_terminated().cstring(), addressof tv) + 0 == @_utime64[I32](path.cstring(), addressof tv) else var tv: (ILong, ILong, ILong, ILong) = (atime._1.ilong(), atime._2.ilong() / 1000, mtime._1.ilong(), mtime._2.ilong() / 1000) - 0 == @utimes[I32](path.null_terminated().cstring(), addressof tv) + 0 == @utimes[I32](path.cstring(), addressof tv) end diff --git a/packages/files/path.pony b/packages/files/path.pony index c76ea5ffc5..22ef844245 100644 --- a/packages/files/path.pony +++ b/packages/files/path.pony @@ -492,7 +492,7 @@ primitive Path isn't one. """ let cstring = @pony_os_realpath[Pointer[U8] iso^]( - path.null_terminated().cstring()) + path.cstring()) if cstring.is_null() then error diff --git a/packages/net/dns.pony b/packages/net/dns.pony index f50eb57112..075606b740 100644 --- a/packages/net/dns.pony +++ b/packages/net/dns.pony @@ -48,13 +48,13 @@ primitive DNS """ Returns true if the host is a literal IPv4 address. """ - @pony_os_host_ip4[Bool](host.null_terminated().cstring()) + @pony_os_host_ip4[Bool](host.cstring()) fun is_ip6(host: String): Bool => """ Returns true if the host is a literal IPv6 address. """ - @pony_os_host_ip6[Bool](host.null_terminated().cstring()) + @pony_os_host_ip6[Bool](host.cstring()) fun _resolve(auth: DNSLookupAuth, family: U32, host: String, service: String): Array[IPAddress] iso^ @@ -64,7 +64,7 @@ primitive DNS """ var list = recover Array[IPAddress] end var result = @pony_os_addrinfo[Pointer[U8]](family, - host.null_terminated().cstring(), service.null_terminated().cstring()) + host.cstring(), service.cstring()) if not result.is_null() then var addr = result diff --git a/packages/net/ssl/ssl.pony b/packages/net/ssl/ssl.pony index 9e280fc166..f5cb16b5b3 100644 --- a/packages/net/ssl/ssl.pony +++ b/packages/net/ssl/ssl.pony @@ -53,7 +53,7 @@ class SSL not DNS.is_ip6(_hostname) then // SSL_set_tlsext_host_name - @SSL_ctrl(_ssl, 55, 0, _hostname.null_terminated().cstring()) + @SSL_ctrl(_ssl, 55, 0, _hostname.cstring()) end if server then @@ -98,10 +98,10 @@ class SSL end _read_buf.undefined(offset + len) - @SSL_read[I32](_ssl, _read_buf.cstring().usize() + offset, len.i32()) + @SSL_read[I32](_ssl, _read_buf.cpointer().usize() + offset, len.i32()) else _read_buf.undefined(offset + len) - let r = @SSL_read[I32](_ssl, _read_buf.cstring().usize() + offset, + let r = @SSL_read[I32](_ssl, _read_buf.cpointer().usize() + offset, len.i32()) if r <= 0 then @@ -135,14 +135,14 @@ class SSL if _state isnt SSLReady then error end if data.size() > 0 then - @SSL_write[I32](_ssl, data.cstring(), data.size().u32()) + @SSL_write[I32](_ssl, data.cpointer(), data.size().u32()) end fun ref receive(data: ByteSeq) => """ When data is received, add it to the SSL session. """ - @BIO_write[I32](_input, data.cstring(), data.size().u32()) + @BIO_write[I32](_input, data.cpointer(), data.size().u32()) if _state is SSLHandshake then let r = @SSL_do_handshake[I32](_ssl) @@ -172,7 +172,7 @@ class SSL if len == 0 then error end let buf = recover Array[U8].undefined(len) end - @BIO_read[I32](_output, buf.cstring(), buf.size().u32()) + @BIO_read[I32](_output, buf.cpointer(), buf.size().u32()) buf fun ref dispose() => diff --git a/packages/net/ssl/ssl_context.pony b/packages/net/ssl/ssl_context.pony index 00feafb88c..cb36d7b36d 100644 --- a/packages/net/ssl/ssl_context.pony +++ b/packages/net/ssl/ssl_context.pony @@ -63,9 +63,9 @@ class val SSLContext (cert.path.size() == 0) or (key.path.size() == 0) or (0 == @SSL_CTX_use_certificate_chain_file[I32]( - _ctx, cert.path.null_terminated().cstring())) or + _ctx, cert.path.cstring())) or (0 == @SSL_CTX_use_PrivateKey_file[I32]( - _ctx, key.path.null_terminated().cstring(), I32(1))) or + _ctx, key.path.cstring(), I32(1))) or (0 == @SSL_CTX_check_private_key[I32](_ctx)) then error @@ -104,7 +104,7 @@ class val SSLContext if _ctx.is_null() or (0 == @SSL_CTX_set_cipher_list[I32](_ctx, - ciphers.null_terminated().cstring())) + ciphers.cstring())) then error end diff --git a/packages/net/tcp_connection.pony b/packages/net/tcp_connection.pony index 38286b50f7..db0f9d736c 100644 --- a/packages/net/tcp_connection.pony +++ b/packages/net/tcp_connection.pony @@ -74,8 +74,8 @@ actor TCPConnection _max_size = max_size _notify = consume notify _connect_count = @pony_os_connect_tcp[U32](this, - host.null_terminated().cstring(), service.null_terminated().cstring(), - from.null_terminated().cstring()) + host.cstring(), service.cstring(), + from.cstring()) _notify_connecting() new ip4(auth: TCPConnectionAuth, notify: TCPConnectionNotify iso, @@ -90,8 +90,8 @@ actor TCPConnection _max_size = max_size _notify = consume notify _connect_count = @pony_os_connect_tcp4[U32](this, - host.null_terminated().cstring(), service.null_terminated().cstring(), - from.null_terminated().cstring()) + host.cstring(), service.cstring(), + from.cstring()) _notify_connecting() new ip6(auth: TCPConnectionAuth, notify: TCPConnectionNotify iso, @@ -106,8 +106,8 @@ actor TCPConnection _max_size = max_size _notify = consume notify _connect_count = @pony_os_connect_tcp6[U32](this, - host.null_terminated().cstring(), service.null_terminated().cstring(), - from.null_terminated().cstring()) + host.cstring(), service.cstring(), + from.cstring()) _notify_connecting() new _accept(listen: TCPListener, notify: TCPConnectionNotify iso, fd: U32, @@ -293,7 +293,7 @@ actor TCPConnection ifdef windows then try // Add an IOCP write. - @pony_os_send[USize](_event, data.cstring(), data.size()) ? + @pony_os_send[USize](_event, data.cpointer(), data.size()) ? _pending.push((data, 0)) end else @@ -301,7 +301,7 @@ actor TCPConnection try // Send as much data as possible. var len = - @pony_os_send[USize](_event, data.cstring(), data.size()) ? + @pony_os_send[USize](_event, data.cpointer(), data.size()) ? if len < data.size() then // Send any remaining data later. @@ -364,7 +364,7 @@ actor TCPConnection // Write as much data as possible. let len = @pony_os_send[USize](_event, - data.cstring().usize() + offset, data.size() - offset) ? + data.cpointer().usize() + offset, data.size() - offset) ? if (len + offset) < data.size() then // Send remaining data later. @@ -431,7 +431,7 @@ actor TCPConnection try @pony_os_recv[USize]( _event, - _read_buf.cstring().usize() + _read_len, + _read_buf.cpointer().usize() + _read_len, _read_buf.size() - _read_len) ? else _hard_close() @@ -452,7 +452,7 @@ actor TCPConnection // Read as much data as possible. let len = @pony_os_recv[USize]( _event, - _read_buf.cstring().usize() + _read_len, + _read_buf.cpointer().usize() + _read_len, _read_buf.size() - _read_len) ? match len diff --git a/packages/net/tcp_listener.pony b/packages/net/tcp_listener.pony index 2ef105a0c3..2aa9ec9ae7 100644 --- a/packages/net/tcp_listener.pony +++ b/packages/net/tcp_listener.pony @@ -46,7 +46,7 @@ actor TCPListener _limit = limit _notify = consume notify _event = @pony_os_listen_tcp[AsioEventID](this, - host.null_terminated().cstring(), service.null_terminated().cstring()) + host.cstring(), service.cstring()) _init_size = init_size _max_size = max_size _fd = @pony_asio_event_fd(_event) @@ -62,7 +62,7 @@ actor TCPListener _limit = limit _notify = consume notify _event = @pony_os_listen_tcp4[AsioEventID](this, - host.null_terminated().cstring(), service.null_terminated().cstring()) + host.cstring(), service.cstring()) _init_size = init_size _max_size = max_size _fd = @pony_asio_event_fd(_event) @@ -78,7 +78,7 @@ actor TCPListener _limit = limit _notify = consume notify _event = @pony_os_listen_tcp6[AsioEventID](this, - host.null_terminated().cstring(), service.null_terminated().cstring()) + host.cstring(), service.cstring()) _init_size = init_size _max_size = max_size _fd = @pony_asio_event_fd(_event) diff --git a/packages/net/udp_socket.pony b/packages/net/udp_socket.pony index d2c937aff6..13e83a7421 100644 --- a/packages/net/udp_socket.pony +++ b/packages/net/udp_socket.pony @@ -75,7 +75,7 @@ actor UDPSocket """ _notify = consume notify _event = @pony_os_listen_udp[AsioEventID](this, - host.null_terminated().cstring(), service.null_terminated().cstring()) + host.cstring(), service.cstring()) _fd = @pony_asio_event_fd(_event) @pony_os_sockname[Bool](_fd, _ip) _packet_size = size @@ -91,7 +91,7 @@ actor UDPSocket """ _notify = consume notify _event = @pony_os_listen_udp4[AsioEventID](this, - host.null_terminated().cstring(), service.null_terminated().cstring()) + host.cstring(), service.cstring()) _fd = @pony_asio_event_fd(_event) @pony_os_sockname[Bool](_fd, _ip) _packet_size = size @@ -107,7 +107,7 @@ actor UDPSocket """ _notify = consume notify _event = @pony_os_listen_udp6[AsioEventID](this, - host.null_terminated().cstring(), service.null_terminated().cstring()) + host.cstring(), service.cstring()) _fd = @pony_asio_event_fd(_event) @pony_os_sockname[Bool](_fd, _ip) _packet_size = size @@ -154,7 +154,7 @@ actor UDPSocket revert to allowing the OS to choose, call with an empty string. """ if not _closed then - @pony_os_multicast_interface[None](_fd, from.null_terminated().cstring()) + @pony_os_multicast_interface[None](_fd, from.cstring()) end be set_multicast_loopback(loopback: Bool) => @@ -181,8 +181,8 @@ actor UDPSocket specific interface. """ if not _closed then - @pony_os_multicast_join[None](_fd, group.null_terminated().cstring(), - to.null_terminated().cstring()) + @pony_os_multicast_join[None](_fd, group.cstring(), + to.cstring()) end be multicast_leave(group: String, to: String = "") => @@ -192,8 +192,8 @@ actor UDPSocket previously added this group. """ if not _closed then - @pony_os_multicast_leave[None](_fd, group.null_terminated().cstring(), - to.null_terminated().cstring()) + @pony_os_multicast_leave[None](_fd, group.cstring(), + to.cstring()) end be dispose() => @@ -260,7 +260,7 @@ actor UDPSocket let size = _packet_size let data = _read_buf = recover Array[U8].undefined(size) end let from = recover IPAddress end - let len = @pony_os_recvfrom[USize](_event, data.cstring(), + let len = @pony_os_recvfrom[USize](_event, data.cpointer(), data.space(), from) ? if len == 0 then @@ -317,7 +317,7 @@ actor UDPSocket """ ifdef windows then try - @pony_os_recvfrom[USize](_event, _read_buf.cstring(), + @pony_os_recvfrom[USize](_event, _read_buf.cpointer(), _read_buf.space(), _read_from) ? else _readable = false @@ -331,7 +331,7 @@ actor UDPSocket """ if not _closed then try - @pony_os_sendto[USize](_fd, data.cstring(), data.size(), to) ? + @pony_os_sendto[USize](_fd, data.cpointer(), data.size(), to) ? else _close() end diff --git a/packages/process/process_monitor.pony b/packages/process/process_monitor.pony index 233e5b238a..a500574349 100644 --- a/packages/process/process_monitor.pony +++ b/packages/process/process_monitor.pony @@ -268,8 +268,8 @@ actor ProcessMonitor _dup2(_stdin_read, _STDINFILENO()) // redirect stdin _dup2(_stdout_write, _STDOUTFILENO()) // redirect stdout _dup2(_stderr_write, _STDERRFILENO()) // redirect stderr - if 0 > @execve[I32](path.null_terminated().cstring(), argp.cstring(), - envp.cstring()) + if 0 > @execve[I32](path.cstring(), argp.cpointer(), + envp.cpointer()) then @_exit[None](I32(-1)) end @@ -295,7 +295,7 @@ actor ProcessMonitor """ let argv = Array[Pointer[U8] tag](args.size() + 1) for s in args.values() do - argv.push(s.null_terminated().cstring()) + argv.push(s.cstring()) end argv.push(Pointer[U8]) // nullpointer to terminate list of args argv @@ -379,7 +379,7 @@ actor ProcessMonitor ifdef posix then let d = data if _stdin_write > 0 then - let res = @write[ISize](_stdin_write, d.cstring(), d.size()) + let res = @write[ISize](_stdin_write, d.cpointer(), d.size()) if res < 0 then _notifier.failed(this, WriteError) end @@ -412,7 +412,7 @@ actor ProcessMonitor ifdef posix then let d = data if _stdin_write > 0 then - let res = @write[ISize](_stdin_write, d.cstring(), d.size()) + let res = @write[ISize](_stdin_write, d.cpointer(), d.size()) if res < 0 then _notifier.failed(this, WriteError) end @@ -557,7 +557,7 @@ actor ProcessMonitor if fd == -1 then return false end var sum: USize = 0 while true do - let len = @read[ISize](fd, _read_buf.cstring().usize() + _read_len, + let len = @read[ISize](fd, _read_buf.cpointer().usize() + _read_len, _read_buf.size() - _read_len) let errno = @pony_os_errno() let next = _read_buf.space() diff --git a/packages/regex/match.pony b/packages/regex/match.pony index 76dd21201c..57e64191b3 100644 --- a/packages/regex/match.pony +++ b/packages/regex/match.pony @@ -51,7 +51,7 @@ class Match let out = recover A(len) end len = out.space() - rc = @pcre2_substring_copy_bynumber_8[I32](_match, i, out.cstring(), + rc = @pcre2_substring_copy_bynumber_8[I32](_match, i, out.cpointer(), addressof len) if rc != 0 then error end out.truncate(len) @@ -75,7 +75,7 @@ class Match let out = recover A(len) end len = out.space() - @pcre2_substring_copy_byname_8[I32](_match, name.cstring(), out.cstring(), + @pcre2_substring_copy_byname_8[I32](_match, name.cstring(), out.cpointer(), addressof len) out.truncate(len) out diff --git a/packages/regex/regex.pony b/packages/regex/regex.pony index f89c9578e6..b7a2a04f8a 100644 --- a/packages/regex/regex.pony +++ b/packages/regex/regex.pony @@ -63,7 +63,7 @@ class Regex var err: I32 = 0 var erroffset: USize = 0 - _pattern = @pcre2_compile_8[Pointer[_Pattern]](from.cstring(), from.size(), + _pattern = @pcre2_compile_8[Pointer[_Pattern]](from.cpointer(), from.size(), _PCRE2.utf(), addressof err, addressof erroffset, Pointer[U8]) if _pattern.is_null() then @@ -124,8 +124,8 @@ class Regex repeat rc = @pcre2_substitute_8[I32](_pattern, - subject.cstring(), subject.size(), offset, opt, Pointer[U8], - Pointer[U8], value.cstring(), value.size(), out.cstring(), + subject.cpointer(), subject.size(), offset, opt, Pointer[U8], + Pointer[U8], value.cpointer(), value.size(), out.cpointer(), addressof len) if rc == -48 then @@ -174,7 +174,7 @@ class Regex does not exist. """ let rc = @pcre2_substring_number_from_name[I32](_pattern, - name.null_terminated().cstring()) + name.cstring()) if rc < 0 then error @@ -206,10 +206,10 @@ class Regex Pointer[U8]) let rc = if _jit then - @pcre2_jit_match_8[I32](_pattern, subject.cstring(), subject.size(), + @pcre2_jit_match_8[I32](_pattern, subject.cpointer(), subject.size(), offset, options, m, Pointer[U8]) else - @pcre2_match_8[I32](_pattern, subject.cstring(), subject.size(), offset, + @pcre2_match_8[I32](_pattern, subject.cpointer(), subject.size(), offset, options, m, Pointer[U8]) end diff --git a/packages/time/date.pony b/packages/time/date.pony index 6c3d7b58dd..00c10fcb1f 100644 --- a/packages/time/date.pony +++ b/packages/time/date.pony @@ -41,6 +41,5 @@ class Date Format the time as for strftime. """ recover - String.from_cstring(@ponyint_formattime[Pointer[U8]](this, - fmt.null_terminated().cstring())) + String.from_cstring(@ponyint_formattime[Pointer[U8]](this, fmt.cstring())) end From 396259e8ccb6c0185665e411c41945b33444c27e Mon Sep 17 00:00:00 2001 From: Sergey Avseyev Date: Wed, 19 Oct 2016 22:09:14 +0300 Subject: [PATCH 6/9] Fix symlink flags. FreeBSD does not have -r --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 143bec2060..bb5cf43309 100644 --- a/Makefile +++ b/Makefile @@ -81,7 +81,7 @@ ifdef destdir endif endif -ifeq ($(OSTYPE),osx) +ifneq (,$(filter $(OSTYPE), osx freebsd)) symlink.flags = -sf else symlink.flags = -srf From f517ea97de10c778dc32c94623d76c0a5dd9437f Mon Sep 17 00:00:00 2001 From: Benoit Vey Date: Wed, 19 Oct 2016 21:50:16 +0200 Subject: [PATCH 7/9] Restrict mutable tuple recovery (#1124) Closes #1123. --- CHANGELOG.md | 1 + src/libponyc/type/alias.c | 117 ++++++++++++++++++++++++++++++++------ src/libponyc/type/alias.h | 2 + src/libponyc/type/cap.c | 16 ++++++ src/libponyc/type/cap.h | 2 + test/libponyc/recover.cc | 67 ++++++++++++++++++++++ 6 files changed, 189 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b4ed1d7d6..3e83b08754 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to the Pony compiler and standard library will be documented ### Fixed - Compiling ponyrt with Clang versions >= 3.3, < 3.6. +- Restrict mutable tuple recovery to maintain reference capability security (issue #1123) ### Added diff --git a/src/libponyc/type/alias.c b/src/libponyc/type/alias.c index 3b391d90e5..4585c504af 100644 --- a/src/libponyc/type/alias.c +++ b/src/libponyc/type/alias.c @@ -330,33 +330,60 @@ ast_t* consume_type(ast_t* type, token_id cap) return NULL; } -ast_t* recover_type(ast_t* type, token_id cap) +static ast_t* recover_complex(ast_t* type, token_id cap) { switch(ast_id(type)) { case TK_UNIONTYPE: case TK_ISECTTYPE: case TK_TUPLETYPE: + break; + + default: + assert(false); + break; + } + + // recover each element + ast_t* r_type = ast_from(type, ast_id(type)); + ast_t* child = ast_child(type); + + while(child != NULL) + { + ast_t* r_right = recover_type(child, cap); + + if(r_right == NULL) { - // recover each element - ast_t* r_type = ast_from(type, ast_id(type)); - ast_t* child = ast_child(type); + ast_free_unattached(r_type); + return NULL; + } - while(child != NULL) - { - ast_t* r_right = recover_type(child, cap); + ast_append(r_type, r_right); + child = ast_sibling(child); + } - if(r_right == NULL) - { - ast_free_unattached(r_type); - return NULL; - } + return r_type; +} - ast_append(r_type, r_right); - child = ast_sibling(child); - } +ast_t* recover_type(ast_t* type, token_id cap) +{ + switch(ast_id(type)) + { + case TK_UNIONTYPE: + case TK_ISECTTYPE: + return recover_complex(type, cap); - return r_type; + case TK_TUPLETYPE: + { + if((cap == TK_VAL) || (cap == TK_BOX) || (cap == TK_TAG)) + return recover_complex(type, cap); + + if(immutable_or_opaque(type)) + return recover_complex(type, cap); + + // If we're recovering to something writable, we can't lift the reference + // capability because the objects in the tuple might alias each other. + return ast_dup(type); } case TK_NOMINAL: @@ -448,3 +475,61 @@ bool sendable(ast_t* type) assert(0); return false; } + +bool immutable_or_opaque(ast_t* type) +{ + switch(ast_id(type)) + { + case TK_UNIONTYPE: + case TK_ISECTTYPE: + case TK_TUPLETYPE: + { + // Test each element. + ast_t* child = ast_child(type); + + while(child != NULL) + { + if(!immutable_or_opaque(child)) + return false; + + child = ast_sibling(child); + } + + return true; + } + + case TK_ARROW: + { + ast_t* lower = viewpoint_lower(type); + + if(lower == NULL) + return false; + + bool ok = immutable_or_opaque(lower); + ast_free_unattached(lower); + return ok; + } + + case TK_NOMINAL: + { + AST_GET_CHILDREN(type, pkg, id, typeparams, cap, eph); + return cap_immutable_or_opaque(ast_id(cap)); + } + + case TK_TYPEPARAMREF: + { + AST_GET_CHILDREN(type, id, cap, eph); + return cap_immutable_or_opaque(ast_id(cap)); + } + + case TK_FUNTYPE: + case TK_INFERTYPE: + case TK_ERRORTYPE: + return false; + + default: {} + } + + assert(0); + return false; +} diff --git a/src/libponyc/type/alias.h b/src/libponyc/type/alias.h index 770604a70a..47d9d6ae11 100644 --- a/src/libponyc/type/alias.h +++ b/src/libponyc/type/alias.h @@ -19,6 +19,8 @@ ast_t* recover_type(ast_t* type, token_id cap); bool sendable(ast_t* type); +bool immutable_or_opaque(ast_t* type); + PONY_EXTERN_C_END #endif diff --git a/src/libponyc/type/cap.c b/src/libponyc/type/cap.c index 3549010fc9..d7965942d8 100644 --- a/src/libponyc/type/cap.c +++ b/src/libponyc/type/cap.c @@ -1076,6 +1076,22 @@ bool cap_sendable(token_id cap) return false; } +bool cap_immutable_or_opaque(token_id cap) +{ + switch(cap) + { + case TK_VAL: + case TK_BOX: + case TK_TAG: + case TK_CAP_SHARE: + return true; + + default: {} + } + + return false; +} + bool cap_safetowrite(token_id into, token_id cap) { switch(into) diff --git a/src/libponyc/type/cap.h b/src/libponyc/type/cap.h index 62b4aed37c..521e5a9fd0 100644 --- a/src/libponyc/type/cap.h +++ b/src/libponyc/type/cap.h @@ -77,6 +77,8 @@ bool cap_view_lower(token_id left_cap, token_id left_eph, bool cap_sendable(token_id cap); +bool cap_immutable_or_opaque(token_id cap); + bool cap_safetowrite(token_id into, token_id cap); PONY_EXTERN_C_END diff --git a/test/libponyc/recover.cc b/test/libponyc/recover.cc index 5a85c82fdc..2d0bd1230b 100644 --- a/test/libponyc/recover.cc +++ b/test/libponyc/recover.cc @@ -375,3 +375,70 @@ TEST_F(RecoverTest, CantDoPartialApplication_RefWithLowerToTag) TEST_ERRORS_1(src, "receiver type is not a subtype of target type"); } + +TEST_F(RecoverTest, CanRecover_TupleMutableAlias) +{ + const char* src = + "class Foo\n" + " fun apply() =>\n" + " let x: (Foo, Foo) = recover\n" + " let y: Foo = Foo\n" + " (y, y)\n" + " end"; + + TEST_COMPILE(src); +} + +TEST_F(RecoverTest, CantRecover_TupleMutableLift) +{ + const char* src = + "class Foo\n" + " fun apply() =>\n" + " let x: (Foo iso, Foo iso) = recover\n" + " let y: Foo = Foo\n" + " (y, y)\n" + " end"; + + TEST_ERRORS_1(src, "right side must be a subtype of left side"); +} + +TEST_F(RecoverTest, CanRecover_TupleMutableToImmutable) +{ + const char* src = + "class Foo\n" + " fun apply() =>\n" + " let x: (Foo val, Foo val) = recover val\n" + " let y: Foo = Foo\n" + " (y, y)\n" + " end"; + + TEST_COMPILE(src); +} + +TEST_F(RecoverTest, CanRecover_TupleInUnionNoInnerLift) +{ + const char* src = + "class Foo\n" + " fun apply() =>\n" + " let x: (Foo iso | (Foo, Foo)) = recover\n" + " let y: Foo = Foo\n" + " let z: (Foo | (Foo, Foo)) = (y, y)\n" + " z\n" + " end"; + + TEST_COMPILE(src); +} + +TEST_F(RecoverTest, CantRecover_TupleInUnionInnerLift) +{ + const char* src = + "class Foo\n" + " fun apply() =>\n" + " let x: (Foo iso | (Foo iso, Foo iso)) = recover\n" + " let y: Foo = Foo\n" + " let z: (Foo | (Foo, Foo)) = (y, y)\n" + " z\n" + " end"; + + TEST_ERRORS_1(src, "right side must be a subtype of left side"); +} From 0f8ebb5863811a6f706a2cee3df83bc3a4e9a2ba Mon Sep 17 00:00:00 2001 From: Benoit Vey Date: Sun, 16 Oct 2016 22:27:07 +0200 Subject: [PATCH 8/9] Use a starvation-free algorithm in the MPMC queues We now use a spinlock with starvation-free guarantees instead of a CAS loop (lock-free) in the scheduler MPMC queues. The same change is made to the pool allocator free-list storage. This change removes issues with the ABA problem handling. --- CHANGELOG.md | 1 + src/common/pony/detail/atomics.h | 4 ++ src/libponyrt/mem/pool.c | 109 +++++++++++++++++-------------- src/libponyrt/sched/mpmcq.c | 52 +++++++-------- src/libponyrt/sched/mpmcq.h | 2 + 5 files changed, 93 insertions(+), 75 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e83b08754..0da5b0bd89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ All notable changes to the Pony compiler and standard library will be documented - Compiling ponyrt with Clang versions >= 3.3, < 3.6. - Restrict mutable tuple recovery to maintain reference capability security (issue #1123) +- Crash in the runtime scheduler queues ### Added diff --git a/src/common/pony/detail/atomics.h b/src/common/pony/detail/atomics.h index 4f858fde2c..4f32afb5dc 100644 --- a/src/common/pony/detail/atomics.h +++ b/src/common/pony/detail/atomics.h @@ -27,6 +27,7 @@ using std::atomic_compare_exchange_weak_explicit; using std::atomic_compare_exchange_strong_explicit; using std::atomic_fetch_add_explicit; using std::atomic_fetch_sub_explicit; +using std::atomic_thread_fence; # endif #elif defined(__GNUC__) && !defined(__clang__) # include @@ -116,6 +117,9 @@ using std::atomic_fetch_sub_explicit; __atomic_fetch_sub(PTR, VAL, MO); \ }) +# define atomic_thread_fence(MO) \ + __atomic_thread_fence(MO) + # undef PONY_ATOMIC_BUILTINS #endif diff --git a/src/libponyrt/mem/pool.c b/src/libponyrt/mem/pool.c index 8da04a0b57..fb32323dc6 100644 --- a/src/libponyrt/mem/pool.c +++ b/src/libponyrt/mem/pool.c @@ -3,6 +3,7 @@ #include "pool.h" #include "alloc.h" #include "../ds/fun.h" +#include "../sched/cpu.h" #include #include #include @@ -67,6 +68,8 @@ typedef struct pool_global_t size_t size; size_t count; PONY_ATOMIC(pool_central_t*) central; + PONY_ATOMIC(size_t )ticket; + PONY_ATOMIC(size_t) waiting_for; } pool_global_t; /// An item on a thread-local list of free blocks. @@ -87,22 +90,22 @@ typedef struct pool_block_header_t static pool_global_t pool_global[POOL_COUNT] = { - {POOL_MIN << 0, POOL_MAX / (POOL_MIN << 0), NULL}, - {POOL_MIN << 1, POOL_MAX / (POOL_MIN << 1), NULL}, - {POOL_MIN << 2, POOL_MAX / (POOL_MIN << 2), NULL}, - {POOL_MIN << 3, POOL_MAX / (POOL_MIN << 3), NULL}, - {POOL_MIN << 4, POOL_MAX / (POOL_MIN << 4), NULL}, - {POOL_MIN << 5, POOL_MAX / (POOL_MIN << 5), NULL}, - {POOL_MIN << 6, POOL_MAX / (POOL_MIN << 6), NULL}, - {POOL_MIN << 7, POOL_MAX / (POOL_MIN << 7), NULL}, - {POOL_MIN << 8, POOL_MAX / (POOL_MIN << 8), NULL}, - {POOL_MIN << 9, POOL_MAX / (POOL_MIN << 9), NULL}, - {POOL_MIN << 10, POOL_MAX / (POOL_MIN << 10), NULL}, - {POOL_MIN << 11, POOL_MAX / (POOL_MIN << 11), NULL}, - {POOL_MIN << 12, POOL_MAX / (POOL_MIN << 12), NULL}, - {POOL_MIN << 13, POOL_MAX / (POOL_MIN << 13), NULL}, - {POOL_MIN << 14, POOL_MAX / (POOL_MIN << 14), NULL}, - {POOL_MIN << 15, POOL_MAX / (POOL_MIN << 15), NULL}, + {POOL_MIN << 0, POOL_MAX / (POOL_MIN << 0), NULL, 0, 0}, + {POOL_MIN << 1, POOL_MAX / (POOL_MIN << 1), NULL, 0, 0}, + {POOL_MIN << 2, POOL_MAX / (POOL_MIN << 2), NULL, 0, 0}, + {POOL_MIN << 3, POOL_MAX / (POOL_MIN << 3), NULL, 0, 0}, + {POOL_MIN << 4, POOL_MAX / (POOL_MIN << 4), NULL, 0, 0}, + {POOL_MIN << 5, POOL_MAX / (POOL_MIN << 5), NULL, 0, 0}, + {POOL_MIN << 6, POOL_MAX / (POOL_MIN << 6), NULL, 0, 0}, + {POOL_MIN << 7, POOL_MAX / (POOL_MIN << 7), NULL, 0, 0}, + {POOL_MIN << 8, POOL_MAX / (POOL_MIN << 8), NULL, 0, 0}, + {POOL_MIN << 9, POOL_MAX / (POOL_MIN << 9), NULL, 0, 0}, + {POOL_MIN << 10, POOL_MAX / (POOL_MIN << 10), NULL, 0, 0}, + {POOL_MIN << 11, POOL_MAX / (POOL_MIN << 11), NULL, 0, 0}, + {POOL_MIN << 12, POOL_MAX / (POOL_MIN << 12), NULL, 0, 0}, + {POOL_MIN << 13, POOL_MAX / (POOL_MIN << 13), NULL, 0, 0}, + {POOL_MIN << 14, POOL_MAX / (POOL_MIN << 14), NULL, 0, 0}, + {POOL_MIN << 15, POOL_MAX / (POOL_MIN << 15), NULL, 0, 0}, }; static __pony_thread_local pool_local_t pool_local[POOL_COUNT]; @@ -465,8 +468,6 @@ static void pool_free_pages(void* p, size_t size) static void pool_push(pool_local_t* thread, pool_global_t* global) { - pool_central_t* cmp; - pool_central_t* xchg; pool_central_t* p = (pool_central_t*)thread->pool; p->length = thread->length; @@ -476,53 +477,63 @@ static void pool_push(pool_local_t* thread, pool_global_t* global) assert(p->length == global->count); TRACK_PUSH((pool_item_t*)p, p->length, global->size); - cmp = atomic_load_explicit(&global->central, memory_order_acquire); + size_t my_ticket = atomic_fetch_add_explicit(&global->ticket, 1, + memory_order_relaxed); - uintptr_t mask = UINTPTR_MAX ^ ((1 << (POOL_MIN_BITS - 1)) - 1); + while(my_ticket != atomic_load_explicit(&global->waiting_for, + memory_order_relaxed)) + ponyint_cpu_relax(); - do - { - // We know the alignment boundary of the objects in the stack so we use the - // low bits for ABA protection. - uintptr_t aba = (uintptr_t)cmp & ~mask; - p->central = (pool_central_t*)((uintptr_t)cmp & mask); - - xchg = (pool_central_t*)((uintptr_t)p | ((aba + 1) & ~mask)); - } while(!atomic_compare_exchange_weak_explicit(&global->central, &cmp, xchg, - memory_order_release, memory_order_relaxed)); + atomic_thread_fence(memory_order_acquire); + + pool_central_t* top = atomic_load_explicit(&global->central, + memory_order_relaxed); + p->central = top; + + atomic_store_explicit(&global->central, p, memory_order_relaxed); + atomic_store_explicit(&global->waiting_for, my_ticket + 1, + memory_order_release); } static pool_item_t* pool_pull(pool_local_t* thread, pool_global_t* global) { - pool_central_t* cmp; - pool_central_t* xchg; - pool_central_t* next; + // If we believe the global free list is empty, bailout immediately without + // taking a ticket to avoid unnecessary contention. + if(atomic_load_explicit(&global->central, memory_order_relaxed) == NULL) + return NULL; - cmp = atomic_load_explicit(&global->central, memory_order_acquire); + size_t my_ticket = atomic_fetch_add_explicit(&global->ticket, 1, + memory_order_relaxed); + + while(my_ticket != atomic_load_explicit(&global->waiting_for, + memory_order_relaxed)) + ponyint_cpu_relax(); - uintptr_t mask = UINTPTR_MAX ^ ((1 << (POOL_MIN_BITS - 1)) - 1); + atomic_thread_fence(memory_order_acquire); - do + pool_central_t* top = atomic_load_explicit(&global->central, + memory_order_relaxed); + + if(top == NULL) { - // We know the alignment boundary of the objects in the stack so we use the - // low bits for ABA protection. - uintptr_t aba = (uintptr_t)cmp & ~mask; - next = (pool_central_t*)((uintptr_t)cmp & mask); + atomic_store_explicit(&global->waiting_for, my_ticket + 1, + memory_order_relaxed); + return NULL; + } - if(next == NULL) - return NULL; + pool_central_t* next = top->central; - xchg = (pool_central_t*)((uintptr_t)next->central | ((aba + 1) & ~mask)); - } while(!atomic_compare_exchange_weak_explicit(&global->central, &cmp, xchg, - memory_order_acq_rel, memory_order_relaxed)); + atomic_store_explicit(&global->central, next, memory_order_relaxed); + atomic_store_explicit(&global->waiting_for, my_ticket + 1, + memory_order_release); - pool_item_t* p = (pool_item_t*)next; + pool_item_t* p = (pool_item_t*)top; - assert(next->length == global->count); - TRACK_PULL(p, next->length, global->size); + assert(top->length == global->count); + TRACK_PULL(p, top->length, global->size); thread->pool = p->next; - thread->length = next->length - 1; + thread->length = top->length - 1; return p; } diff --git a/src/libponyrt/sched/mpmcq.c b/src/libponyrt/sched/mpmcq.c index 4fc9717ec4..b57996589b 100644 --- a/src/libponyrt/sched/mpmcq.c +++ b/src/libponyrt/sched/mpmcq.c @@ -20,6 +20,8 @@ void ponyint_mpmcq_init(mpmcq_t* q) atomic_store_explicit(&q->head, node, memory_order_relaxed); atomic_store_explicit(&q->tail, node, memory_order_relaxed); + atomic_store_explicit(&q->ticket, 0, memory_order_relaxed); + atomic_store_explicit(&q->waiting_for, 0, memory_order_relaxed); } void ponyint_mpmcq_destroy(mpmcq_t* q) @@ -56,37 +58,33 @@ void ponyint_mpmcq_push_single(mpmcq_t* q, void* data) void* ponyint_mpmcq_pop(mpmcq_t* q) { - mpmcq_node_t* cmp; - mpmcq_node_t* xchg; - mpmcq_node_t* next; - mpmcq_node_t* tail; + size_t my_ticket = atomic_fetch_add_explicit(&q->ticket, 1, + memory_order_relaxed); - cmp = atomic_load_explicit(&q->tail, memory_order_acquire); + while(my_ticket != atomic_load_explicit(&q->waiting_for, + memory_order_relaxed)) + ponyint_cpu_relax(); - uintptr_t mask = UINTPTR_MAX ^ - ((1 << (POOL_MIN_BITS + POOL_INDEX(sizeof(mpmcq_node_t)) - 1)) - 1); + atomic_thread_fence(memory_order_acquire); - do + mpmcq_node_t* tail = atomic_load_explicit(&q->tail, memory_order_relaxed); + // Get the next node rather than the tail. The tail is either a stub or has + // already been consumed. + mpmcq_node_t* next = atomic_load_explicit(&tail->next, memory_order_relaxed); + + // Bailout if we have no next node. + if(next == NULL) { - // We know the alignment boundary of the objects in the queue so we use the - // low bits for ABA protection. - uintptr_t aba = (uintptr_t)cmp & ~mask; - tail = (mpmcq_node_t*)((uintptr_t)cmp & mask); + atomic_store_explicit(&q->waiting_for, my_ticket + 1, memory_order_relaxed); + return NULL; + } - // Get the next node rather than the tail. The tail is either a stub or has - // already been consumed. - next = atomic_load_explicit(&tail->next, memory_order_acquire); - - // Bailout if we have no next node. - if(next == NULL) - return NULL; - - // Make the next node the tail, incrementing the aba counter. If this - // fails, cmp becomes the new tail and we retry the loop. - xchg = (mpmcq_node_t*)((uintptr_t)next | ((aba + 1) & ~mask)); - } while(!atomic_compare_exchange_weak_explicit(&q->tail, &cmp, xchg, - memory_order_acq_rel, memory_order_relaxed)); + atomic_store_explicit(&q->tail, next, memory_order_relaxed); + atomic_store_explicit(&q->waiting_for, my_ticket + 1, memory_order_release); + // Synchronise-with the push. + atomic_thread_fence(memory_order_acquire); + // We'll return the data pointer from the next node. void* data = atomic_load_explicit(&next->data, memory_order_relaxed); @@ -96,9 +94,11 @@ void* ponyint_mpmcq_pop(mpmcq_t* q) // the old tail is NULL. atomic_store_explicit(&next->data, NULL, memory_order_release); - while(atomic_load_explicit(&tail->data, memory_order_acquire) != NULL) + while(atomic_load_explicit(&tail->data, memory_order_relaxed) != NULL) ponyint_cpu_relax(); + atomic_thread_fence(memory_order_acquire); + // Free the old tail. The new tail is the next node. POOL_FREE(mpmcq_node_t, tail); return data; diff --git a/src/libponyrt/sched/mpmcq.h b/src/libponyrt/sched/mpmcq.h index 3c68275ba8..e969a96a77 100644 --- a/src/libponyrt/sched/mpmcq.h +++ b/src/libponyrt/sched/mpmcq.h @@ -14,6 +14,8 @@ __pony_spec_align__( { PONY_ATOMIC(mpmcq_node_t*) head; PONY_ATOMIC(mpmcq_node_t*) tail; + PONY_ATOMIC(size_t) ticket; + PONY_ATOMIC(size_t) waiting_for; } mpmcq_t, 64 ); From 984d7d1c84ba98aecc82d23cf8931384e4881d78 Mon Sep 17 00:00:00 2001 From: Sean T Allen Date: Thu, 20 Oct 2016 00:11:40 -0400 Subject: [PATCH 9/9] Prep for 0.6.0 release --- CHANGELOG.md | 2 +- VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0da5b0bd89..4898170da0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to the Pony compiler and standard library will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/) and [Keep a CHANGELOG](http://keepachangelog.com/). -## [unreleased] - unreleased +## [0.6.0] - 2016-10-20 ### Fixed diff --git a/VERSION b/VERSION index 4b9fcbec10..a918a2aa18 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.5.1 +0.6.0