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 74f5b230e2..4898170da0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ 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/). +## [0.6.0] - 2016-10-20 + +### Fixed + +- 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 + +- DTrace and SystemTap support - `use=dtrace` + +### 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 ### Fixed diff --git a/Makefile b/Makefile index 83951fbbf5..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 @@ -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/VERSION b/VERSION index 4b9fcbec10..a918a2aa18 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.5.1 +0.6.0 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/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/_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 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 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/common/pony/detail/atomics.h b/src/common/pony/detail/atomics.h index 08b1318943..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 @@ -55,7 +56,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 @@ -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/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/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/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 ); 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 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"); +}