From 5cc1181dccf1ebde99acfb7e39254cb611a172cc Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Wed, 4 Mar 2020 14:50:25 -0800 Subject: [PATCH 01/10] Start work on chooser scenario --- .../working-notes/scenarios/chooser.md | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 proposals/interface-types/working-notes/scenarios/chooser.md diff --git a/proposals/interface-types/working-notes/scenarios/chooser.md b/proposals/interface-types/working-notes/scenarios/chooser.md new file mode 100644 index 00000000..90ae17c2 --- /dev/null +++ b/proposals/interface-types/working-notes/scenarios/chooser.md @@ -0,0 +1,104 @@ +# Choosing Strings + +## Preamble +This note is part of a series of notes that gives complete end-to-end examples +of using Interface Types to export and import functions. + +## Introduction + +Keeping track of memory that is allocated in order to allow string processing +can be challenging. This example illustrates an extreme case that involves +non-determinism. + +This scenario is based on the idea of using run-time information in order to +control the flow of information. In particular, consider the C++ chooser +function: + +```C++ +typedef std::shared_ptr shared_string; + +shared_string nondeterministic_choice(shared_string one, shared_string two) { + return random() > 0.5 ? std::move(one) : std::move(two); +} +``` + +This function takes two string arguments and returns one of them. Regardless of +the merits of this particular function, it sets up significant challenges should +we want to expose it using Interface Types. Specifically, there are two +resources entering the function, with just one leaving. However, when exposed as +an Interface Type function, all these resources must be created and properly +disposed of within the adapter code itself. + +This note focuses on the techniques that enable this to be acheived reliably. + +## Exporting the Chooser + +The Interface Type function signature for our chooser is simple: the function +takes two `string`s and returns one: + +```wasm +(@interface func (export "chooser") + (param $left string) + (param $right string) + (result string) + ... +) +``` + +One of the specific challenges in this scenario is the handling of shared +pointers. We 'require' that the core `nondeterministic_choice` function honor +the semantics of proper reference counting the input arguments and the returned +string. + +However, there are actually _two_ schemes in play in this scenario: the +Interface Types management of resources and the C++ implementation of +`shared_ptr`. Effectively, in addition to lifting and lowering the `string` +values we must also lift and lower the ownership between Interface Types and +core WASM. + +In this scenario we take a somewhat simplified view of C++'s implementation of +shared pointers: a shared pointer is implemented as a pair consisting of a +reference count and a raw pointer to the shared resource. + +>Note: In practice, the C++ implementation of shared pointers is somewhat more +>complex; for reasons that are not important here. + +This results in _two_ memory allocations for the shared resource: one for the +resource itself and one for the pointer structure -- which contains a reference +count and a raw pointer to the resource. + + +```wasm +(@interface func (export "chooser") + (param $left string) + (param $right string) + (result string) + local.get $left + string.size + call $malloc + own (i32) + call $free + end + let (local $l owned) + local.get $left + local.get $l + own.project ;; pick up actual resource + local.get $left + string.size + string-to-memory "memx" + local.get $right + string.size + call $malloc + own (i32) + call $free + end + let (local $r owned) + local.get $right + local.get $r + own.project + local.get $right + string.size + string-to-memory "memx" + +``` + From 44481d511471f7a11235a0d253d6c87b1ce8389f Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Tue, 10 Mar 2020 08:16:51 -0700 Subject: [PATCH 02/10] Start work on chooser scenario --- .gitignore | 1 + .../working-notes/scenarios/chooser.md | 73 ++++++++++++------- 2 files changed, 49 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 6739bb2b..8bdc30e3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ **/*.pyc **/_build **/_output +**/*.html diff --git a/proposals/interface-types/working-notes/scenarios/chooser.md b/proposals/interface-types/working-notes/scenarios/chooser.md index 90ae17c2..f722d5fd 100644 --- a/proposals/interface-types/working-notes/scenarios/chooser.md +++ b/proposals/interface-types/working-notes/scenarios/chooser.md @@ -17,8 +17,8 @@ function: ```C++ typedef std::shared_ptr shared_string; -shared_string nondeterministic_choice(shared_string one, shared_string two) { - return random() > 0.5 ? std::move(one) : std::move(two); +shared_string nondeterministic_choice(shared_string&& one, shared_string&& two) { + return random() > 0.5 ? one : two; } ``` @@ -29,7 +29,7 @@ resources entering the function, with just one leaving. However, when exposed as an Interface Type function, all these resources must be created and properly disposed of within the adapter code itself. -This note focuses on the techniques that enable this to be acheived reliably. +This note focuses on the techniques that enable this to be achieved reliably. ## Exporting the Chooser @@ -73,32 +73,55 @@ count and a raw pointer to the resource. (param $left string) (param $right string) (result string) + local.get $left + dup string.size - call $malloc - own (i32) - call $free - end - let (local $l owned) - local.get $left - local.get $l - own.project ;; pick up actual resource - local.get $left - string.size - string-to-memory "memx" + call-export "malloc" + string-to-memory ;; leave the destination on the stack + call-export "shared_builder" + let (local $l i32) + local.get $right + dup string.size - call $malloc - own (i32) - call $free - end - let (local $r owned) - local.get $right - local.get $r - own.project - local.get $right - string.size - string-to-memory "memx" + call-export "malloc" + string-to-memory + call-export "shared_builder" ;; build a shared_ptr structure + let (local $r i32) + ;; set up the call to the core chooser + local.get $p1 + local.get $p2 + call-export "chooser_" ;; call the chooser itself + call-export "shared_moveout" ;; actual value + own (i32) ;; own the result + call-export "free" ;; will eventually call to free string + end + memory-to-string + local.get $p2 ;; the shared_ptr structures + call-export "shared_release" + end + local.get $p1 + call-export "shared_release" + end +end ``` +The returned value from `chooser_` is wrapped up as an `own`ed allocation +and lifted to a `string`. + +>Note we do not need to `own` the string memory of the arguments because we have +>asserted that both the arguments to `chooser_` will be the _last_ references to +>the strings. However, only one of the C++ strings will be deallocated -- the +>other is returned to us. We _do_ need to `own` the return result, however. + +In addition to the Interface Types management, because we are using a +non-trivial C++ structure, we have to invoke the appropriate constructors, +access functions and destructors of our `shared_ptr` structure. + +This is achieved through the calls to `shared_builder`, `shared_moveout` and +`shared_release` functions exported by the core WebAssembly module. + + + From 2bd625416e38a69947a9bdacfedb619902f19f7c Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Thu, 12 Mar 2020 15:54:30 -0700 Subject: [PATCH 03/10] Small edit --- .../working-notes/scenarios/chooser.md | 77 ++++++++++++++++--- 1 file changed, 68 insertions(+), 9 deletions(-) diff --git a/proposals/interface-types/working-notes/scenarios/chooser.md b/proposals/interface-types/working-notes/scenarios/chooser.md index f722d5fd..b88a3aeb 100644 --- a/proposals/interface-types/working-notes/scenarios/chooser.md +++ b/proposals/interface-types/working-notes/scenarios/chooser.md @@ -17,8 +17,8 @@ function: ```C++ typedef std::shared_ptr shared_string; -shared_string nondeterministic_choice(shared_string&& one, shared_string&& two) { - return random() > 0.5 ? one : two; +shared_string nondeterministic_choice(shared_string one, shared_string two) { + return random() > 0.5 ? std::move(one) : std_move(two); } ``` @@ -92,7 +92,7 @@ count and a raw pointer to the resource. ;; set up the call to the core chooser local.get $p1 local.get $p2 - call-export "chooser_" ;; call the chooser itself + call-export "nondeterministic_choice" ;; call the chooser itself call-export "shared_moveout" ;; actual value own (i32) ;; own the result call-export "free" ;; will eventually call to free string @@ -108,20 +108,79 @@ count and a raw pointer to the resource. end ``` -The returned value from `chooser_` is wrapped up as an `own`ed allocation -and lifted to a `string`. +The returned value from `nondeterministic_choice` is wrapped up as an `own`ed +allocation and lifted to a `string`. >Note we do not need to `own` the string memory of the arguments because we have ->asserted that both the arguments to `chooser_` will be the _last_ references to ->the strings. However, only one of the C++ strings will be deallocated -- the ->other is returned to us. We _do_ need to `own` the return result, however. +>asserted that both the arguments to `nondeterministic_choice` will be the +>_last_ references to the strings. However, only one of the C++ strings will be +>deallocated -- the other is returned to us. We _do_ need to `own` the return +>result, however. In addition to the Interface Types management, because we are using a non-trivial C++ structure, we have to invoke the appropriate constructors, access functions and destructors of our `shared_ptr` structure. This is achieved through the calls to `shared_builder`, `shared_moveout` and -`shared_release` functions exported by the core WebAssembly module. +`shared_release` functions exported by the core WebAssembly module. These +construct the shared pointer (with the actual string as argument), extract the +string and release the structure respectively. + +## Calling the chooser + +We shall assume that the import to `nondeterministic_choice` were as though it +was from a core WebAssembly import whose signature is: + +```wasm +(func (import "" "chooser_") + (param i32 i32 i32 i32) + (result i32 i32)) +``` + +The two strings are passed as pairs of memory address and length, and the return +is similarly returned as a pair of `i32` numbers. + +>Note that although _we_ believe that the returned value will be the same as one +>of the arguments, the limitations of Interface Types mean that the returned +>string will be a copy of one of the arguments. + +The import adapter for `chooser` has to lift the two argument `string`s and +lower the return value: + +```wasm +(@interface implement (import "" "chooser_") + (param $lp i32) + (param $ll i32) + (param $rp i32) + (param $rl i32) + (result i32 i32) + + local.get $lp + local.get $ll + memory-to-string + + local.get $rp + local.get $rl + memory-to-string + + call-import "chooser" ;; leaves a string on stack + + let (local $res string) + local.get $res + dup + string.size + call-export "malloc" ;; local malloc + string-to-memory + local.get $res + string.size ;; return size as second result + end +) +``` + +Compared to the export adapter, the import adapter is very straightforward. This +is because we require the caller -- a core wasm function -- to take +responsibility for the argument strings and for the returned string. + From adeb400d76e8f3c3ba16da8ffda0db84bc866391 Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Fri, 27 Mar 2020 14:44:09 -0700 Subject: [PATCH 04/10] Initial draft of non-deterministic string chooser scenario --- .../working-notes/scenarios/chooser.md | 606 ++++++++++++++++-- 1 file changed, 550 insertions(+), 56 deletions(-) diff --git a/proposals/interface-types/working-notes/scenarios/chooser.md b/proposals/interface-types/working-notes/scenarios/chooser.md index b88a3aeb..81f56f5e 100644 --- a/proposals/interface-types/working-notes/scenarios/chooser.md +++ b/proposals/interface-types/working-notes/scenarios/chooser.md @@ -69,45 +69,81 @@ count and a raw pointer to the resource. ```wasm +(@interface func $stralloc-x + (param $str string) + (result owned) ;; return an owned shared ptr of the string + local.get $str + string.size + call-export "malloc-x" ;; create string entity of right size + own (i32) + call-export "free-x" ;; temporary, until safely re-owned + end + let (local $ptr owned) (result owned) + local.get $str + local.get $ptr + owned.access ;; access the owned pointer + local.get $str + string.size + utf8.from.string + local.get $ptr + end +) + +(@interface func $shared-x + (param $ptr owned) + (result owned) ;; return a shared ptr of the entity + + local.get $ptr + owned.release ;; access pointer and remove ownership + i32.const 1 ;; initial reference count of 1 + call-export "shared-builder-x" ;; make it a shared ptr pair + own (i32) ;; re-own it, with a different destructor + call-export "shared-release-x" + end +end +) + (@interface func (export "chooser") (param $left string) (param $right string) (result string) local.get $left - dup - string.size - call-export "malloc" - string-to-memory ;; leave the destination on the stack - call-export "shared_builder" - let (local $l i32) - + invoke-func $stralloc-x + invoke-func $shared-x ;; make it a shared ptr + let (local $lpo owned) (result string) local.get $right - dup - string.size - call-export "malloc" - string-to-memory - call-export "shared_builder" ;; build a shared_ptr structure - let (local $r i32) + invoke-func $stralloc-x + invoke-func $shared-x ;; make it a shared ptr + let (local $rp owned) (result string) ;; set up the call to the core chooser - local.get $p1 - local.get $p2 + local.get $lp + owned.access + local.get $rp + owned.access call-export "nondeterministic_choice" ;; call the chooser itself - call-export "shared_moveout" ;; actual value own (i32) ;; own the result - call-export "free" ;; will eventually call to free string - end - memory-to-string - - local.get $p2 ;; the shared_ptr structures - call-export "shared_release" + call-export "shared-release-x" ;; will eventually call to free string + end + owned.access + call-export "shared-get-x" + call-export "access-utf8-x" + string.from.utf8 end - local.get $p1 - call-export "shared_release" end -end +) ``` +For clarity, we separate out two helper functions -- `shared-x` and `stralloc-x` +-- whose role is to create a shared pointer pair and to instantiate a memory +string from an Interface Types's `string` entity. + +>Note the `-x` suffix signals that these functions are part of the exporting +>module. + +>Note, like the import and export adapters themselves, they will be inlined as +>part of the adapter fusion process. + The returned value from `nondeterministic_choice` is wrapped up as an `own`ed allocation and lifted to a `string`. @@ -117,14 +153,31 @@ allocation and lifted to a `string`. >deallocated -- the other is returned to us. We _do_ need to `own` the return >result, however. -In addition to the Interface Types management, because we are using a +In addition to the Interface Types memory management, because we are using a non-trivial C++ structure, we have to invoke the appropriate constructors, access functions and destructors of our `shared_ptr` structure. -This is achieved through the calls to `shared_builder`, `shared_moveout` and -`shared_release` functions exported by the core WebAssembly module. These -construct the shared pointer (with the actual string as argument), extract the -string and release the structure respectively. +This is achieved through some additional core helper functions that are exported +by the core wasm module, although they would not typically be exported as +Interface Type functions: + +* `shared-builder-x` is used to create and initialize a `shared_ptr` + structure. It takes a pointer as an argument and returns a `shared_ptr` with + an initial reference count of 1. + +* `shared-release-x` is used to decrement the reference count of a `shared_ptr`; + and if that results in the reference count dropping to zero _both the + underlying resource and the `shared_ptr` structure itself are disposed of_. + +* `shared-get-x` is used to access the underlying resource of a `shared_ptr` + without affecting it's reference count. + +* `access-utf8-x` is used to access the memory pointer for a string's text and + its length. + +In practice there would likely be additional support functions -- such as a +version of accessing a shared resource whilst incrementing reference +count. However, we do not need them in this scenario. ## Calling the chooser @@ -133,54 +186,495 @@ was from a core WebAssembly import whose signature is: ```wasm (func (import "" "chooser_") - (param i32 i32 i32 i32) - (result i32 i32)) + (param i32 i32) + (result i32)) ``` -The two strings are passed as pairs of memory address and length, and the return -is similarly returned as a pair of `i32` numbers. +The two strings are passed as memory addresses of structures -- from which the +text of the string and its length can be ascertained. The structure itself is +unspecified; we will use access functions -- such as `access-utf8` as needed. >Note that although _we_ believe that the returned value will be the same as one >of the arguments, the limitations of Interface Types mean that the returned >string will be a copy of one of the arguments. -The import adapter for `chooser` has to lift the two argument `string`s and +The import adapter for `chooser_` has to lift the two argument `string`s and lower the return value: ```wasm +(@interface func $stralloc-i + (param $str string) + (result i32) ;; return a shared ptr of the string + local.get $str + string.size + call-export "malloc-i" ;; create string entity of right size + let (local $ptr i32) (result i32) + local.get $str + local.get $ptr + local.get $str + string.size + utf8.from.string + local.get $ptr + end +) + (@interface implement (import "" "chooser_") - (param $lp i32) - (param $ll i32) - (param $rp i32) - (param $rl i32) - (result i32 i32) + (param $l i32) + (param $r i32) + (result i32) - local.get $lp - local.get $ll - memory-to-string + local.get $l + call-export "access-utf8-i" ;; get at the text & length of the left string + string.from.utf8 - local.get $rp - local.get $rl - memory-to-string + local.get $r + call-export "access-utf8-i" ;; the right string + string.from.utf8 call-import "chooser" ;; leaves a string on stack - let (local $res string) - local.get $res - dup - string.size - call-export "malloc" ;; local malloc - string-to-memory - local.get $res - string.size ;; return size as second result - end -) + invoke-func "stralloc-i" ;; allocate a local string for the result +) ``` Compared to the export adapter, the import adapter is very straightforward. This is because we require the caller -- a core wasm function -- to take responsibility for the argument strings and for the returned string. +## Fusing adapters + +The fused adapter consists of the inlined export adapter within the import +adapter; which is then simplified. In this case we have a simple import adapter +being combined with one that has some complexity: + +```wasm +(@interface implement (import "" "chooser_") + (param $l i32) + (param $r i32) + (result i32) + + local.get $l + call-export "access-utf8-i" + string.from.utf8 + + local.get $r + call-export "access-utf8-i" + string.from.utf8 + + let ($left string + $right string) (result string) + local.get $left + invoke-func $stralloc-x + invoke-func $shared-x ;; make it a shared ptr + let (local $lp owned) (result string) + local.get $right + invoke-func $stralloc-x + invoke-func $shared-x ;; make it a shared ptr + let (local $rp owned) (result string) + ;; set up the call to the core chooser + local.get $lp + owned.access + local.get $rp + owned.access + call-export "nondeterministic_choice" ;; call the chooser itself + own (i32) ;; own the result + call-export "shared-release-x" + end + owned.access + call-export "shared-get-x" + call-export "access-utf8-x" + string.from.utf8 + end + end + end + invoke-func "stralloc-i" ;; allocate a local string for the result +) +``` + +After we expand the helper functions, we get: + +```wasm2 +(@interface implement (import "" "chooser_") + (param $l i32) + (param $r i32) + (result i32) + + local.get $l + call-export "access-utf8-i" + string.from.utf8 + + local.get $r + call-export "access-utf8-i" + string.from.utf8 + + let ($left string + $right string) + local.get $left + string.size + call-export "malloc-x" ;; create string entity of right size + own (i32) ;; until we can safely package + call-export "free-x" + end + let (local $ptr owned)(result owned) + local.get $left + local.get $ptr + owned.access + local.get $left + string.size + utf8.from.string + local.get $ptr + end + owned.release ;; release the owned + i32.const 1 ;; initial reference count of 1 + call-export "shared-builder-x" ;; make it a shared ptr pair + own (i32) + call-export "shared-release-x" + end + let (local $lp i32) (result string) + local.get $right + string.size + call-export "malloc-x" ;; create string entity of right size + own (i32) ;; until we can safely package + call-export "free-x" + end + let (local $ptr owned)(result owned) + local.get $right + local.get $ptr + owned.access + local.get $right + string.size + utf8.from.string + local.get $ptr + end + owned.release + i32.const 1 ;; initial reference count of 1 + call-export "shared-builder-x" ;; make it a shared ptr pair + own (i32) + call-export "shared-release-x" + end + let (local $rp owned) (result string) + ;; set up the call to the core chooser + local.get $lp + owned.access + local.get $rp + owned.access + call-export "nondeterministic_choice" ;; call the chooser itself + own (i32) ;; own the result + call-export "shared-release-x" + end + owned.access + call-export "shared-get-x" + call-export "access-utf8-x" + string.from.utf8 + end + end + end + let (local $str string)(result i32) + local.get $str + string.size + call-export "malloc-i" ;; create string entity of right size + let (local $ptr i32)(result i32) + local.get $str + local.get $ptr + local.get $str + string.size + utf8.from.string + local.get $ptr ;; our final return value + end + end +) +``` + +Reordering to bring parameter use closer to definition + +```wasm3 +(@interface implement (import "" "chooser_") + (param $l i32) + (param $r i32) + (result i32) + + local.get $l + call-export "access-utf8-i" + string.from.utf8 + + let ($left string) (result string) + local.get $left + string.size + call-export "malloc-x" ;; create string entity of right size + own (i32) ;; until we can safely package + call-export "free-x" + end + let (local $ptr owned)(result owned) + local.get $left + local.get $ptr + owned.access + local.get $left + string.size + utf8.from.string + local.get $ptr + end + owned.release ;; release the owned + i32.const 1 ;; initial reference count of 1 + call-export "shared-builder-x" ;; make it a shared ptr pair + own (i32) + call-export "shared-release-x" + end + let (local $lp owned) (result string) + local.get $r + call-export "access-utf8-i" + string.from.utf8 + let ($right string)(result string) + local.get $right + invoke-func $stralloc-x + string.size + call-export "malloc-x" ;; create string entity of right size + own (i32) ;; until we can safely package + call-export "free-x" + end + let (local $ptr owned) + local.get $right + local.get $ptr + owned.access + local.get $right + string.size + utf8.from.string + local.get $ptr + end + owned.release + i32.const 1 ;; initial reference count of 1 + call-export "shared-builder-x" ;; make it a shared ptr pair + own (i32) + call-export "shared-release-x" + end + let (local $rp owned) (result owned) + ;; set up the call to the core chooser + local.get $lp + owned.access + local.get $rp + owned.access + call-export "nondeterministic_choice" ;; call the chooser itself + own (i32) ;; own the result + call-export "shared-release-x" + end + owned.access + call-export "shared-get-x" + call-export "access-utf8-x" + string.from.utf8 + end + end + end + end + let (local $str string)(result i32) + local.get $str + string.size + call-export "malloc-x" ;; create string entity of right size + let (local $ptr i32) + local.get $str + local.get $ptr + local.get $str + string.size + utf8.from.string + local.get $ptr ;; our final return value + end + end +) +``` + +Folding and fusing the string lifting and lowering operators: + + +```wasm4 +(@interface implement (import "" "chooser_") + (param $l i32) + (param $r i32) + (result i32) + + local.get $l + call-export "access-utf8-i" ;; return base & len + + let (local $lbase i32)(local $lsize i32)(result i32) + local.get $lbase + local.get $lsize + call-export "malloc-x" + own (i32) + call-export "free-x" + end + let (local $ptr owned)(result owned) + local.get $lbase + local.get $ptr + owned.access + local.get $lsize + memory.copy "mem-i" "mem-x" ;; copy string across + local.get $ptr + owned.release + i32.const 1 ;; initial reference count of 1 + call-export "shared-builder-x" ;; make it a shared ptr pair + own (i32) ;; $lp is owned + call-export "shared-release-x" + end + end + let (local $lp owned) (result i32) + local.get $r + call-export "access-utf8-i" + let (local $rbase i32)(local $rsize i32)(result i32) + local.get $rbase + local.get $rsize + call-export "malloc-x" + own (i32) + call-export "free-x" + end + let (local $ptr owned)(result owned) + local.get $rbase + local.get $rsize + local.get $ptr + owned.access + local.get $rsize + memory.copy "mem-i" "mem-x" ;; copy string across + local.get $ptr + owned.release + i32.const 1 ;; initial reference count of 1 + call-export "shared-builder-x" ;; make it a shared ptr pair + own (i32) + call-export "shared-release-x" + end + end + let (local $rp i32)(result i32) + local.get $lp + owned.access + local.get $rp + owned.access + call-export "nondeterministic_choice" ;; call the chooser itself + own (i32) ;; own the result + call-export "shared-release-x" + end + owned.access + call-export "shared-get-x" + call-export "access-utf8-x" + let (local $xbase i32) (local $xsize i32) (result i32) + local.get $xsize + call-export "malloc-i" ;; create string entity of right size + let (local $ptr i32) + local.get $xbase + local.get $ptr + local.get $xsize + memory.copy "mem-x" "mem-i" + local.get $ptr ;; our final return value + end + end + end + end + end + end +) +``` + +Our final step is unwrapping the `own`ed blocks and moving their contents to the +correct place in the final adapter. This requires us to know where in the code +the last reference to the owned value are. + +This is achieved in two phases: creating local variables that reference the +stack values captured by the `own` instructions, and then moving the `own`ed +block of instructions to the appropriate location. + +Note that the various `owned.access` instructions disappear at this point. + +In this case, the `$lp` and `$rp` values have no mention after the call to +`"nondterministic_choice"`, and so we can move the `"shared-release"` calls to +just after that call. Similarly, the release of the return value can be +performed after returned string has been copied into the importing module: + +```wasm5 +(@interface implement (import "" "chooser_") + (param $l i32) + (param $r i32) + (result i32) + (local $o1 i32) ;; left string shared-ptr memory + (local $o2 i32) ;; right string shared-ptr memory + (local $o3 i32) + + local.get $l + call-export "access-utf8-i" ;; return base & len + + let (local $lbase i32)(local $lsize i32)(result i32) + local.get $lbase + local.get $lsize + call-export "malloc-x" + let (local $ptr i32)(result i32) + local.get $lbase + local.get $ptr + local.get $lsize + memory.copy "mem-i" "mem-x" ;; copy string across + local.get $ptr + i32.const 1 ;; initial reference count of 1 + call-export "shared-builder-x" ;; make it a shared ptr pair + local.tee $o1 + end + let (local $lp i32) (result i32) + local.get $r + call-export "access-utf8-i" + let (local $rbase i32)(local $rsize i32)(result i32) + local.get $rbase + local.get $rsize + call-export "malloc-x" + let (local $ptr i32)(result i32) + local.get $rbase + local.get $rsize + local.get $ptr + local.get $rsize + memory.copy "mem-i" "mem-x" ;; copy string across + local.get $ptr + i32.const 1 ;; initial reference count of 1 + call-export "shared-builder-x" ;; make it a shared ptr pair + local.tee $o2 + end + let (local $rp i32)(result i32) + local.get $lp + local.get $rp + call-export "nondeterministic_choice" ;; call the chooser itself + local.tee $o3 + call-export "shared-get-x" + call-export "access-utf8-x" + let (local $xbase i32) (local $xsize i32) (result i32) + local.get $xsize + call-export "malloc-i" ;; create string entity of right size + let (local $ptr i32) + local.get $xbase + local.get $ptr + local.get $xsize + memory.copy "mem-x" "mem-i" + local.get $ptr ;; our final return value + end + end + end + end + end + end + local.get $o1 + call-export "shared-release-x" + local.get $o2 + call-export "shared-release-x" + local.get $o3 + call-export "shared-release-x" +) +``` + +For simplicity, we migrated all the deallocations to the end of the fused +adapter. In some situations, for example when processing arrays, we may wish to +be more aggressive in invoking the memory release code. + +Although fairly long, this fused adapter has a striaghtfoward structure: the +input strings are copied from the import memory to the export memory -- and also +wrapped as shared pointer structures as required by the signature of the +`nondeterministic_chooser` function. The resulting string is copied from the +export localtion to the import memory. And, finally, any temporary structures +allocated are released. + +Note that the string returned by `nondeterministic_chooser` is not directly +freed -- using a call to `"free"` -- is _released_. This is because the return +is also a shared pointer and we are required merely to decrement the reference +count. +We also decrement the reference count of the created argument strings -- just in +case the callee keeps an additional reference to either one. From c1aac7f65fd76d704ccf370cc3e7373e549ef658 Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Tue, 7 Apr 2020 12:08:25 -0700 Subject: [PATCH 05/10] Reflect some of the reviewers' comments. --- .../working-notes/scenarios/chooser.md | 475 ++++-------------- 1 file changed, 94 insertions(+), 381 deletions(-) diff --git a/proposals/interface-types/working-notes/scenarios/chooser.md b/proposals/interface-types/working-notes/scenarios/chooser.md index 81f56f5e..dbdc1bb1 100644 --- a/proposals/interface-types/working-notes/scenarios/chooser.md +++ b/proposals/interface-types/working-notes/scenarios/chooser.md @@ -71,27 +71,26 @@ count and a raw pointer to the resource. ```wasm (@interface func $stralloc-x (param $str string) - (result owned) ;; return an owned shared ptr of the string + (result (owned i32)) ;; return an owned shared ptr of the string local.get $str string.size call-export "malloc-x" ;; create string entity of right size own (i32) call-export "free-x" ;; temporary, until safely re-owned end - let (local $ptr owned) (result owned) - local.get $str + let (local $ptr (owned i32)) (result (owned i32)) + (utf8.from.string + (local.get $str) + (owned.access (local.get $ptr)) ;; access the owned pointer + (string.size + (local.get $str))) local.get $ptr - owned.access ;; access the owned pointer - local.get $str - string.size - utf8.from.string - local.get $ptr end ) -(@interface func $shared-x - (param $ptr owned) - (result owned) ;; return a shared ptr of the entity +(@interface func $sharedalloc-x + (param $ptr (owned i32)) + (result (owned i32)) ;; return a shared ptr of the entity local.get $ptr owned.release ;; access pointer and remove ownership @@ -108,35 +107,26 @@ end (param $right string) (result string) - local.get $left - invoke-func $stralloc-x - invoke-func $shared-x ;; make it a shared ptr - let (local $lpo owned) (result string) - local.get $right - invoke-func $stralloc-x - invoke-func $shared-x ;; make it a shared ptr - let (local $rp owned) (result string) - ;; set up the call to the core chooser - local.get $lp - owned.access - local.get $rp - owned.access - call-export "nondeterministic_choice" ;; call the chooser itself - own (i32) ;; own the result - call-export "shared-release-x" ;; will eventually call to free string - end - owned.access - call-export "shared-get-x" - call-export "access-utf8-x" - string.from.utf8 - end + (call-export "nondeterministic_choice" ;; call the chooser itself + (invoke-func $sharedalloc-x ;; make it a shared ptr + (invoke-func $stralloc-x + (local.get $left))) + (invoke-func $sharedalloc-x ;; make it a shared ptr + (invoke-func $stralloc-x + local.get $right))) + own (i32) ;; own the result + call-export "shared-release-x" ;; will eventually call to free string end + owned.access + (string.from.utf8 + (call-export "access-utf8-x" + (call-export "shared-get-ptr-x"))) ) ``` -For clarity, we separate out two helper functions -- `shared-x` and `stralloc-x` --- whose role is to create a shared pointer pair and to instantiate a memory -string from an Interface Types's `string` entity. +For clarity, we separate out two helper functions -- `sharedalloc-x` and +`stralloc-x` -- whose role is to create a shared pointer pair and to instantiate +a memory string from an Interface Types's `string` entity. >Note the `-x` suffix signals that these functions are part of the exporting >module. @@ -161,19 +151,20 @@ This is achieved through some additional core helper functions that are exported by the core wasm module, although they would not typically be exported as Interface Type functions: -* `shared-builder-x` is used to create and initialize a `shared_ptr` - structure. It takes a pointer as an argument and returns a `shared_ptr` with - an initial reference count of 1. +* `shared-builder-x` : `(T)=>shared_ptr` is used to create and initialize a + `shared_ptr` structure. It takes a pointer as an argument and returns a + `shared_ptr` with an initial reference count of 1. -* `shared-release-x` is used to decrement the reference count of a `shared_ptr`; - and if that results in the reference count dropping to zero _both the - underlying resource and the `shared_ptr` structure itself are disposed of_. +* `shared-release-x` : `(shared_ptr)=>void` is used to decrement the + reference count of a `shared_ptr`; and if that results in the reference count + dropping to zero _both the underlying resource and the `shared_ptr` structure + itself are disposed of_. -* `shared-get-x` is used to access the underlying resource of a `shared_ptr` - without affecting it's reference count. +* `shared-get-ptr-x` : `(shared_ptr)=>T` is used to access the underlying + resource of a `shared_ptr` without affecting it's reference count. -* `access-utf8-x` is used to access the memory pointer for a string's text and - its length. +* `access-utf8-x` : `(std_string)=>(char*,i32)` is used to access the memory + pointer for a string's text and its length. In practice there would likely be additional support functions -- such as a version of accessing a shared resource whilst incrementing reference @@ -194,9 +185,9 @@ The two strings are passed as memory addresses of structures -- from which the text of the string and its length can be ascertained. The structure itself is unspecified; we will use access functions -- such as `access-utf8` as needed. ->Note that although _we_ believe that the returned value will be the same as one ->of the arguments, the limitations of Interface Types mean that the returned ->string will be a copy of one of the arguments. +>Note that although it may seem clear to _the reader_ that the returned value +>must one of the arguments to the call, the limitations of Interface Types mean +>that the returned string will be a copy of one of the arguments. The import adapter for `chooser_` has to lift the two argument `string`s and lower the return value: @@ -204,17 +195,20 @@ lower the return value: ```wasm (@interface func $stralloc-i (param $str string) - (result i32) ;; return a shared ptr of the string + (result (owned i32)) ;; return an owned shared ptr of the string local.get $str string.size call-export "malloc-i" ;; create string entity of right size - let (local $ptr i32) (result i32) - local.get $str + own (i32) + call-export "free-i" ;; temporary, until safely re-owned + end + let (local $ptr (owned i32)) (result (owned i32)) + (utf8.from.string + (local.get $str) + (owned.access (local.get $ptr)) ;; access the owned pointer + (string.size + (local.get $str))) local.get $ptr - local.get $str - string.size - utf8.from.string - local.get $ptr end ) @@ -261,233 +255,40 @@ being combined with one that has some complexity: call-export "access-utf8-i" string.from.utf8 - let ($left string - $right string) (result string) - local.get $left - invoke-func $stralloc-x - invoke-func $shared-x ;; make it a shared ptr - let (local $lp owned) (result string) - local.get $right - invoke-func $stralloc-x - invoke-func $shared-x ;; make it a shared ptr - let (local $rp owned) (result string) - ;; set up the call to the core chooser - local.get $lp - owned.access - local.get $rp - owned.access - call-export "nondeterministic_choice" ;; call the chooser itself - own (i32) ;; own the result - call-export "shared-release-x" - end - owned.access - call-export "shared-get-x" - call-export "access-utf8-x" - string.from.utf8 - end - end - end - invoke-func "stralloc-i" ;; allocate a local string for the result -) -``` - -After we expand the helper functions, we get: - -```wasm2 -(@interface implement (import "" "chooser_") - (param $l i32) - (param $r i32) - (result i32) - - local.get $l - call-export "access-utf8-i" - string.from.utf8 - - local.get $r - call-export "access-utf8-i" - string.from.utf8 - - let ($left string - $right string) - local.get $left - string.size - call-export "malloc-x" ;; create string entity of right size - own (i32) ;; until we can safely package - call-export "free-x" - end - let (local $ptr owned)(result owned) - local.get $left - local.get $ptr - owned.access - local.get $left - string.size - utf8.from.string - local.get $ptr - end - owned.release ;; release the owned - i32.const 1 ;; initial reference count of 1 - call-export "shared-builder-x" ;; make it a shared ptr pair - own (i32) - call-export "shared-release-x" - end - let (local $lp i32) (result string) - local.get $right - string.size - call-export "malloc-x" ;; create string entity of right size - own (i32) ;; until we can safely package - call-export "free-x" - end - let (local $ptr owned)(result owned) - local.get $right - local.get $ptr - owned.access - local.get $right - string.size - utf8.from.string - local.get $ptr - end - owned.release - i32.const 1 ;; initial reference count of 1 - call-export "shared-builder-x" ;; make it a shared ptr pair - own (i32) - call-export "shared-release-x" - end - let (local $rp owned) (result string) - ;; set up the call to the core chooser - local.get $lp - owned.access - local.get $rp - owned.access - call-export "nondeterministic_choice" ;; call the chooser itself - own (i32) ;; own the result - call-export "shared-release-x" - end - owned.access - call-export "shared-get-x" - call-export "access-utf8-x" - string.from.utf8 - end - end - end - let (local $str string)(result i32) - local.get $str - string.size - call-export "malloc-i" ;; create string entity of right size - let (local $ptr i32)(result i32) - local.get $str - local.get $ptr - local.get $str - string.size - utf8.from.string - local.get $ptr ;; our final return value - end - end -) -``` - -Reordering to bring parameter use closer to definition - -```wasm3 -(@interface implement (import "" "chooser_") - (param $l i32) - (param $r i32) - (result i32) - - local.get $l - call-export "access-utf8-i" - string.from.utf8 - - let ($left string) (result string) - local.get $left - string.size - call-export "malloc-x" ;; create string entity of right size - own (i32) ;; until we can safely package - call-export "free-x" - end - let (local $ptr owned)(result owned) - local.get $left - local.get $ptr - owned.access - local.get $left - string.size - utf8.from.string - local.get $ptr - end - owned.release ;; release the owned - i32.const 1 ;; initial reference count of 1 - call-export "shared-builder-x" ;; make it a shared ptr pair - own (i32) - call-export "shared-release-x" - end - let (local $lp owned) (result string) - local.get $r - call-export "access-utf8-i" - string.from.utf8 - let ($right string)(result string) - local.get $right - invoke-func $stralloc-x - string.size - call-export "malloc-x" ;; create string entity of right size - own (i32) ;; until we can safely package - call-export "free-x" - end - let (local $ptr owned) - local.get $right - local.get $ptr - owned.access - local.get $right - string.size - utf8.from.string - local.get $ptr - end - owned.release - i32.const 1 ;; initial reference count of 1 - call-export "shared-builder-x" ;; make it a shared ptr pair - own (i32) - call-export "shared-release-x" - end - let (local $rp owned) (result owned) - ;; set up the call to the core chooser - local.get $lp - owned.access - local.get $rp - owned.access - call-export "nondeterministic_choice" ;; call the chooser itself - own (i32) ;; own the result - call-export "shared-release-x" - end - owned.access - call-export "shared-get-x" - call-export "access-utf8-x" - string.from.utf8 - end - end - end - end - let (local $str string)(result i32) - local.get $str - string.size - call-export "malloc-x" ;; create string entity of right size - let (local $ptr i32) - local.get $str - local.get $ptr - local.get $str - string.size - utf8.from.string - local.get $ptr ;; our final return value + let (local $left string)(local $right string)(result string) + (call-export "nondeterministic_choice" ;; call the chooser itself + (invoke-func $sharedalloc-x ;; make it a shared ptr + (invoke-func $stralloc-x + (local.get $left))) + (invoke-func $sharedalloc-x ;; make it a shared ptr + (invoke-func $stralloc-x + local.get $right))) + own (i32) ;; own the result + call-export "shared-release-x" ;; will eventually call to free string end + owned.access + (string.from.utf8 + (call-export "access-utf8-x" + (call-export "shared-get-ptr-x"))) end + invoke-func "stralloc-i" ;; allocate a local string for the result ) ``` -Folding and fusing the string lifting and lowering operators: +After we expand the helper functions, performing the inlining, fusing pairs of +lifting and lowering operations, memory ownership resolutions, and other +rewriting operations, we get code that no longer has any explicit Interface Type +instructions: -```wasm4 +```wasm (@interface implement (import "" "chooser_") (param $l i32) (param $r i32) (result i32) + (local $o1 i32) ;; left string shared-ptr memory + (local $o2 i32) ;; right string shared-ptr memory + (local $o3 i32) local.get $l call-export "access-utf8-i" ;; return base & len @@ -496,64 +297,45 @@ Folding and fusing the string lifting and lowering operators: local.get $lbase local.get $lsize call-export "malloc-x" - own (i32) - call-export "free-x" - end - let (local $ptr owned)(result owned) + let (local $ptr i32)(result i32) local.get $lbase local.get $ptr - owned.access local.get $lsize memory.copy "mem-i" "mem-x" ;; copy string across local.get $ptr - owned.release i32.const 1 ;; initial reference count of 1 call-export "shared-builder-x" ;; make it a shared ptr pair - own (i32) ;; $lp is owned - call-export "shared-release-x" - end + local.tee $o1 end - let (local $lp owned) (result i32) + let (local $lp i32) (result i32) local.get $r call-export "access-utf8-i" let (local $rbase i32)(local $rsize i32)(result i32) local.get $rbase local.get $rsize call-export "malloc-x" - own (i32) - call-export "free-x" - end - let (local $ptr owned)(result owned) + let (local $ptr i32)(result i32) local.get $rbase - local.get $rsize + local.get $rsize local.get $ptr - owned.access - local.get $rsize + local.get $rsize memory.copy "mem-i" "mem-x" ;; copy string across local.get $ptr - owned.release i32.const 1 ;; initial reference count of 1 call-export "shared-builder-x" ;; make it a shared ptr pair - own (i32) - call-export "shared-release-x" - end + local.tee $o2 end let (local $rp i32)(result i32) local.get $lp - owned.access local.get $rp - owned.access call-export "nondeterministic_choice" ;; call the chooser itself - own (i32) ;; own the result - call-export "shared-release-x" - end - owned.access - call-export "shared-get-x" + local.tee $o3 + call-export "shared-get-ptr-x" call-export "access-utf8-x" let (local $xbase i32) (local $xsize i32) (result i32) local.get $xsize call-export "malloc-i" ;; create string entity of right size - let (local $ptr i32) + let (local $ptr i32)(result i32) local.get $xbase local.get $ptr local.get $xsize @@ -565,12 +347,18 @@ Folding and fusing the string lifting and lowering operators: end end end -) + local.get $o1 + call-export "shared-release-x" + local.get $o2 + call-export "shared-release-x" + local.get $o3 + call-export "shared-release-x" +) ``` -Our final step is unwrapping the `own`ed blocks and moving their contents to the -correct place in the final adapter. This requires us to know where in the code -the last reference to the owned value are. +One of the final steps in this fusing process is unwrapping the `own`ed blocks +and moving their contents to the correct place in the final adapter. This +requires us to know where in the code the last reference to the owned value are. This is achieved in two phases: creating local variables that reference the stack values captured by the `own` instructions, and then moving the `own`ed @@ -583,86 +371,11 @@ In this case, the `$lp` and `$rp` values have no mention after the call to just after that call. Similarly, the release of the return value can be performed after returned string has been copied into the importing module: -```wasm5 -(@interface implement (import "" "chooser_") - (param $l i32) - (param $r i32) - (result i32) - (local $o1 i32) ;; left string shared-ptr memory - (local $o2 i32) ;; right string shared-ptr memory - (local $o3 i32) - - local.get $l - call-export "access-utf8-i" ;; return base & len - - let (local $lbase i32)(local $lsize i32)(result i32) - local.get $lbase - local.get $lsize - call-export "malloc-x" - let (local $ptr i32)(result i32) - local.get $lbase - local.get $ptr - local.get $lsize - memory.copy "mem-i" "mem-x" ;; copy string across - local.get $ptr - i32.const 1 ;; initial reference count of 1 - call-export "shared-builder-x" ;; make it a shared ptr pair - local.tee $o1 - end - let (local $lp i32) (result i32) - local.get $r - call-export "access-utf8-i" - let (local $rbase i32)(local $rsize i32)(result i32) - local.get $rbase - local.get $rsize - call-export "malloc-x" - let (local $ptr i32)(result i32) - local.get $rbase - local.get $rsize - local.get $ptr - local.get $rsize - memory.copy "mem-i" "mem-x" ;; copy string across - local.get $ptr - i32.const 1 ;; initial reference count of 1 - call-export "shared-builder-x" ;; make it a shared ptr pair - local.tee $o2 - end - let (local $rp i32)(result i32) - local.get $lp - local.get $rp - call-export "nondeterministic_choice" ;; call the chooser itself - local.tee $o3 - call-export "shared-get-x" - call-export "access-utf8-x" - let (local $xbase i32) (local $xsize i32) (result i32) - local.get $xsize - call-export "malloc-i" ;; create string entity of right size - let (local $ptr i32) - local.get $xbase - local.get $ptr - local.get $xsize - memory.copy "mem-x" "mem-i" - local.get $ptr ;; our final return value - end - end - end - end - end - end - local.get $o1 - call-export "shared-release-x" - local.get $o2 - call-export "shared-release-x" - local.get $o3 - call-export "shared-release-x" -) -``` - For simplicity, we migrated all the deallocations to the end of the fused adapter. In some situations, for example when processing arrays, we may wish to be more aggressive in invoking the memory release code. -Although fairly long, this fused adapter has a striaghtfoward structure: the +Although fairly long, this fused adapter has a straightforward structure: the input strings are copied from the import memory to the export memory -- and also wrapped as shared pointer structures as required by the signature of the `nondeterministic_chooser` function. The resulting string is copied from the From faf166064d409a40f572c62402fd1813071e365d Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Wed, 8 Apr 2020 11:18:09 -0700 Subject: [PATCH 06/10] Responding to reviewers' comments. Still one outstanding issue wrt form of let instructions. --- .../working-notes/scenarios/chooser.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/proposals/interface-types/working-notes/scenarios/chooser.md b/proposals/interface-types/working-notes/scenarios/chooser.md index dbdc1bb1..3cc84926 100644 --- a/proposals/interface-types/working-notes/scenarios/chooser.md +++ b/proposals/interface-types/working-notes/scenarios/chooser.md @@ -138,10 +138,7 @@ The returned value from `nondeterministic_choice` is wrapped up as an `own`ed allocation and lifted to a `string`. >Note we do not need to `own` the string memory of the arguments because we have ->asserted that both the arguments to `nondeterministic_choice` will be the ->_last_ references to the strings. However, only one of the C++ strings will be ->deallocated -- the other is returned to us. We _do_ need to `own` the return ->result, however. +>wrapped them into `shared_ptr` structures that the callee will manage. In addition to the Interface Types memory management, because we are using a non-trivial C++ structure, we have to invoke the appropriate constructors, @@ -228,6 +225,7 @@ lower the return value: call-import "chooser" ;; leaves a string on stack invoke-func "stralloc-i" ;; allocate a local string for the result + owned.release ;; remove ownership signal at end ) ``` @@ -272,6 +270,7 @@ being combined with one that has some complexity: (call-export "shared-get-ptr-x"))) end invoke-func "stralloc-i" ;; allocate a local string for the result + owned.release ;; remove ownership signal at end ) ``` @@ -383,9 +382,11 @@ export localtion to the import memory. And, finally, any temporary structures allocated are released. Note that the string returned by `nondeterministic_chooser` is not directly -freed -- using a call to `"free"` -- is _released_. This is because the return -is also a shared pointer and we are required merely to decrement the reference -count. +freed -- using a call to `"free-x"` (say). Instead, we invoke the +`shared-release-x` function which decrements the reference count; and if zero +then the memory is freed. This is because the return is also a shared pointer +and it is possible that `nondeterministic_chooser` took an additional reference +to the structure. We also decrement the reference count of the created argument strings -- just in case the callee keeps an additional reference to either one. From 99161b4677f340a5161f55a702189b16e7952e58 Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Wed, 15 Apr 2020 11:42:09 -0700 Subject: [PATCH 07/10] More word smithin --- .../working-notes/scenarios/chooser.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/proposals/interface-types/working-notes/scenarios/chooser.md b/proposals/interface-types/working-notes/scenarios/chooser.md index 3cc84926..946c1698 100644 --- a/proposals/interface-types/working-notes/scenarios/chooser.md +++ b/proposals/interface-types/working-notes/scenarios/chooser.md @@ -10,9 +10,13 @@ Keeping track of memory that is allocated in order to allow string processing can be challenging. This example illustrates an extreme case that involves non-determinism. -This scenario is based on the idea of using run-time information in order to -control the flow of information. In particular, consider the C++ chooser -function: +This scenario is a model of many situations where the actual flow of data +between a call and callee is very difficult to predict. A consequence of that is +that managing memory where one cannot predict which allocated blocks may be +freed when becomes a challenge. + +Our exemplar for this is the non-deterministic C++ chooser function; which +(might) return one of its `string` arguments as its return value: ```C++ typedef std::shared_ptr shared_string; @@ -29,8 +33,6 @@ resources entering the function, with just one leaving. However, when exposed as an Interface Type function, all these resources must be created and properly disposed of within the adapter code itself. -This note focuses on the techniques that enable this to be achieved reliably. - ## Exporting the Chooser The Interface Type function signature for our chooser is simple: the function From ff6756275020eb769ab48cb93915bbe29abbb999 Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Thu, 16 Apr 2020 10:31:21 -0700 Subject: [PATCH 08/10] Some more word-smithing of text. --- .../working-notes/scenarios/chooser.md | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/proposals/interface-types/working-notes/scenarios/chooser.md b/proposals/interface-types/working-notes/scenarios/chooser.md index 946c1698..f014830b 100644 --- a/proposals/interface-types/working-notes/scenarios/chooser.md +++ b/proposals/interface-types/working-notes/scenarios/chooser.md @@ -7,13 +7,12 @@ of using Interface Types to export and import functions. ## Introduction Keeping track of memory that is allocated in order to allow string processing -can be challenging. This example illustrates an extreme case that involves -non-determinism. +can be challenging. This example illustrates an extreme case that involves the +non-deterministic computation of string values. This scenario is a model of many situations where the actual flow of data -between a call and callee is very difficult to predict. A consequence of that is -that managing memory where one cannot predict which allocated blocks may be -freed when becomes a challenge. +between a call and callee can be very difficult to predict. A consequence of +that is that one cannot easily predict when given allocated blocks may be freed. Our exemplar for this is the non-deterministic C++ chooser function; which (might) return one of its `string` arguments as its return value: @@ -26,12 +25,16 @@ shared_string nondeterministic_choice(shared_string one, shared_string two) { } ``` -This function takes two string arguments and returns one of them. Regardless of -the merits of this particular function, it sets up significant challenges should -we want to expose it using Interface Types. Specifically, there are two -resources entering the function, with just one leaving. However, when exposed as -an Interface Type function, all these resources must be created and properly -disposed of within the adapter code itself. +This function takes two string arguments and returns one of them -- at +random. It uses C++ `shared_ptr` structures to try to avoid any unnecessary +copying; whilst also potentially allowing the callee to keep hold of its input +arguments. + +Regardless of the merits of this particular function, it sets up significant +challenges should we want to expose it using Interface Types. Specifically, +there are two resources entering the function, with just one leaving. However, +when exposed as an Interface Type function, all these resources must be created +and properly disposed of within the adapter code itself. ## Exporting the Chooser @@ -43,7 +46,6 @@ takes two `string`s and returns one: (param $left string) (param $right string) (result string) - ... ) ``` @@ -127,8 +129,8 @@ end ``` For clarity, we separate out two helper functions -- `sharedalloc-x` and -`stralloc-x` -- whose role is to create a shared pointer pair and to instantiate -a memory string from an Interface Types's `string` entity. +`stralloc-x` -- whose role respectively is to create a shared pointer pair and +to instantiate a memory string from an Interface Types's `string` entity. >Note the `-x` suffix signals that these functions are part of the exporting >module. @@ -235,6 +237,10 @@ Compared to the export adapter, the import adapter is very straightforward. This is because we require the caller -- a core wasm function -- to take responsibility for the argument strings and for the returned string. +>Note that the `stralloc-i` is functionally identical to its counterpart +>`stralloc-x` -- except that it operates within the import module on the +>latter's memory. + ## Fusing adapters The fused adapter consists of the inlined export adapter within the import From 78725053189536bd746f2abb7c3687a0ff2ea471 Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Fri, 17 Apr 2020 11:32:20 -0700 Subject: [PATCH 09/10] Fix some typos etc. --- .../working-notes/scenarios/chooser.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/proposals/interface-types/working-notes/scenarios/chooser.md b/proposals/interface-types/working-notes/scenarios/chooser.md index f014830b..fdbed860 100644 --- a/proposals/interface-types/working-notes/scenarios/chooser.md +++ b/proposals/interface-types/working-notes/scenarios/chooser.md @@ -21,7 +21,7 @@ Our exemplar for this is the non-deterministic C++ chooser function; which typedef std::shared_ptr shared_string; shared_string nondeterministic_choice(shared_string one, shared_string two) { - return random() > 0.5 ? std::move(one) : std_move(two); + return random() > 0.5 ? std::move(one) : std::move(two); } ``` @@ -98,7 +98,6 @@ count and a raw pointer to the resource. local.get $ptr owned.release ;; access pointer and remove ownership - i32.const 1 ;; initial reference count of 1 call-export "shared-builder-x" ;; make it a shared ptr pair own (i32) ;; re-own it, with a different destructor call-export "shared-release-x" @@ -117,7 +116,7 @@ end (local.get $left))) (invoke-func $sharedalloc-x ;; make it a shared ptr (invoke-func $stralloc-x - local.get $right))) + (local.get $right)))) own (i32) ;; own the result call-export "shared-release-x" ;; will eventually call to free string end @@ -171,6 +170,10 @@ In practice there would likely be additional support functions -- such as a version of accessing a shared resource whilst incrementing reference count. However, we do not need them in this scenario. +>Note: We do not give the code for these helper functions. We expect that, in +>practice, compiler tool chains will construct a range of common helper +>functions that act to reduce the length of individual adapter. + ## Calling the chooser We shall assume that the import to `nondeterministic_choice` were as though it @@ -268,7 +271,7 @@ being combined with one that has some complexity: (local.get $left))) (invoke-func $sharedalloc-x ;; make it a shared ptr (invoke-func $stralloc-x - local.get $right))) + (local.get $right)))) own (i32) ;; own the result call-export "shared-release-x" ;; will eventually call to free string end @@ -310,7 +313,6 @@ instructions: local.get $lsize memory.copy "mem-i" "mem-x" ;; copy string across local.get $ptr - i32.const 1 ;; initial reference count of 1 call-export "shared-builder-x" ;; make it a shared ptr pair local.tee $o1 end @@ -328,7 +330,6 @@ instructions: local.get $rsize memory.copy "mem-i" "mem-x" ;; copy string across local.get $ptr - i32.const 1 ;; initial reference count of 1 call-export "shared-builder-x" ;; make it a shared ptr pair local.tee $o2 end @@ -376,7 +377,7 @@ Note that the various `owned.access` instructions disappear at this point. In this case, the `$lp` and `$rp` values have no mention after the call to `"nondterministic_choice"`, and so we can move the `"shared-release"` calls to just after that call. Similarly, the release of the return value can be -performed after returned string has been copied into the importing module: +performed after returned string has been copied into the importing module. For simplicity, we migrated all the deallocations to the end of the fused adapter. In some situations, for example when processing arrays, we may wish to From e4da862e546637ee370364079d4db583dc1dce36 Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Fri, 17 Apr 2020 13:10:09 -0700 Subject: [PATCH 10/10] remove extra case in .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8bdc30e3..6739bb2b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,3 @@ **/*.pyc **/_build **/_output -**/*.html