From f199ebceab38c405098ce88ac1e1867b2408d8fa Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Fri, 13 Sep 2019 16:06:19 -0700 Subject: [PATCH 01/23] Added some example scenarios --- proposals/interface-types/scenarios.md | 564 +++++++++++++++++++++++++ 1 file changed, 564 insertions(+) create mode 100644 proposals/interface-types/scenarios.md diff --git a/proposals/interface-types/scenarios.md b/proposals/interface-types/scenarios.md new file mode 100644 index 00000000..f7860199 --- /dev/null +++ b/proposals/interface-types/scenarios.md @@ -0,0 +1,564 @@ +# A suite of adapter scenarios + +## Notes +Added let, func.bind, env.get, string.copy instructions. + +Changed arg.get to lcl.get because it is clearer when combined with let. + +## Two Argument Integer Function + +Calling a two argument integer function, should result in effectively zero code. + +### Export + +``` +(@interface func (export "twizzle") + (param $a1 int32)(param $a2 int32) (result int32) + lcl.get $a1 + lcl.get $a2 + call "twizzle_" +) +``` + +### Import + +``` +(@interface func (import "" "twozzle") + (param $a1 int32)(param $a2 int32) (result int32) +) +(@interface implement (import "" "twozzle_") + (param $b1 int32)(param $b2 int32) (result int32) + lcl.get $b1 + lcl.get $b2 + call-import "twozzle" +) + +``` + +### Adapter Code + +``` +(@adapter M2:"twozzle" as M1:"twizzle" + (param $a1 int32)(param $a2 int32) (result int32) + lcl.get $a1 + lcl.get $a2 + call M1:"twizzle_" +) +``` + +This should be viewed as the result of optimizations over an in-line substitution: +``` +(@adapter M2:"twozzle" as M1:"twizzle" + (param $b1 int32)(param $b2 int32) (result int32) + lcl.get $b1 + lcl.get $b2 + let $a1 $a2 + lcl.get $a1 + lcl.get $a2 + call M1:"twizzle_" +) +``` +The `let` pseudo instruction pops elements off the stack and gives them names. + +Inlining the above example, eliminating the combination `lcl.get $b2`; `let $a2` by rewriting `$b2` with `$a2`: +``` +(@adapter M2:"twozzle" as M1:"twizzle" + (param $b1 int32)(param $a2 int32) (result int32) + lcl.get $b1 + let $a1 + lcl.get $a1 + lcl.get $a2 + call M1:"twizzle_" +) +``` +and again for `$b1/$a1` give the result: +``` +(@adapter M2:"twozzle" as M1:"twizzle" + (param $a1 int32)(param $a2 int32) (result int32) + lcl.get $a1 + lcl.get $a2 + call M1:"twizzle_" +) +``` + +Below, we will assume that this transformation is applied automatically; except +where we need to show what happens more clearly. + +## Counting Unicodes + +Passing a string and returning the number of unicode code points in it. + +### Export + +``` +(memory (export "mem1") 1) +(@interface func (export "count") + (param $str string) (result i32) + lcl.get $str + string-to-memory "mem1" "malloc" + call "count_" +) +(func (export "malloc") + (param $sze i32) (result i32) + ... +) +``` + +### Import + +``` +(memory (export "mem2" 1) +(func $count_ (import ("" "count_")) + (param i32 i32) (result i32)) +(@interface func $count (import "" "count") + (param $str string) (result i32)) +(@interface implement (import "" "count_") + (param $ptr i32 $len i32) (result i32)) + lcl.get $ptr + lcl.get $len + memory-to-string "mem2" + call-import $count +) +``` + +### Adapter code + +Note that the type of the adapter is in terms of wasm core types. + +``` +(@adapter M1:"count" as M2:"count" + (param $ptr i32 $len i32) (result i32)) + lcl.get $ptr + lcl.get $len + memory-to-string M2:"mem2" + string-to-memory M1:"mem1" M1:"malloc" + call M2:"count_" +) +``` +which, after collapsing coercion operators, becomes: +``` +(@adapter M1:"count" as M2:"count" + (param $ptr i32 $len i32) (result i32)) + lcl.get $ptr + lcl.get $len + string.copy M2:"mem2" M1:"mem1" M1:"malloc" + call M2:"count_" +) +``` + +This assumes that `string.copy` combines memory allocation, string copy and +returns the new address and repeats the size of the string. + +## Getenv Function + +The `getenv` function builds on `count` by also returning a `string`. + +### Export + + +``` +(memory (export "mem1") 1) +(@interface func (export "getenv") + (param $str string) (result string) + lcl.get $str + string-to-memory "mem1" "malloc" + call "getenv_" + memory-to-string "mem1" +) +(func (export "malloc") + (param $sze i32) (result i32) + ... +) +``` + +### Import + +The importer must also export `malloc` so that the returned string can be +stored. + +``` +(memory (export "mem2" 1) +(func $getenv_ (import ("" "getenv_")) + (param i32 i32) (result i32 i32)) +(@interface func $getenv (import "" "getenv") + (param $str string) (result string)) +(@interface implement (import "" "getenv_") + (param $ptr i32 $len i32) (result i32)) + lcl.get $ptr + lcl.get $len + memory-to-string "mem2" + call-import $getenv + string-to-memory "mem2" "malloc" +) + +(func (export "malloc") + (param $sze i32) (result i32) + ... +) +``` + + +### Adapter + +``` +(@adapter M1:"getenv" as M2:"getenv" + (param $ptr i32 $len i32) (result i32 i32)) + lcl.get $ptr + lcl.get $len + memory-to-string M2:"mem2" + string-to-memory M1:"mem1" M1:"malloc" + call M1:"getenv_" + memory-to-string M1:"mem1" + string-to-memory M2:"mem2" M2:"malloc" +) +``` + +which, after collapsing coercions, becomes + +``` +(@adapter M1:"getenv" as M2:"getenv" + (param $ptr i32 $len i32) (result i32 i32)) + lcl.get $ptr + lcl.get $len + string.copy M2:"mem2" M1:"mem1" M1:"malloc" + call M1:"getenv_" + string.copy M1:"mem1" M2:"mem2" M2:"malloc" +) +``` + +## Fetch Url + +The `fetch` function includes a callback which is invoked whenever 'something +happens' on the session. + +It also introduces two new enumeration types: `ReturnCode` and `StatusCode`. + +The signature of `fetch` as an interface type is: + +``` +fetch:(string,(statusCode,string)=>returnCode)=>returnCode +``` + +The callback requires an inline function definition, which is then bound to a +new closure. + +The `func.bind` adapter instruction takes two literal arguments: a type +specification of the _capture list_ (which is structured in a way that is +analogous to the param section of a function) and a literal function definition. + +The `func.bind` instruction also 'pops off' the stack as many values as are +specified in the capture signature; these are bound into a closure which is +pushed onto the stack. + +### Export + +``` +(@interface datatype @returnCode + (oneof + (enum "ok") + (enum "bad") + ) +) + +(@interface datatype @statusCode + (oneof + (enum "fail") + (enum "havedata") + (enum "eof") + ) +) + +(memory (export "mem1") 1) + +(@interface func (export "fetch") + (param $u string + $cb (func (param $status statusCode $text string) (result returnCode))) + (result string) + lcl.get $u + string-to-memory "mem1" "malloc" + + lcl.get $cb + func.bind (env $ecb (param $status statusCode $text string) (result returnCode)) + (func + (param $status i32 $txt i32 $len i32) + (result i32) + lcl.get $status + int23-to-enum @statusCode + lcl.get $txt + lcl.get $len + memory-to-string "mem1" + env.get $ecb + call-indirect + enum-to-int32 @returnCode + ) + call $fetch_ + int32-to-enum @returnCode +) + +(func (export "malloc") + (param $sze i32) (result i32) + ... +) + +``` + +### Import + +Importing `fetch` implies exporting the function that implements the callback. + +``` +(memory (export "mem2" 1) + +(func $fetch_ (import ("" "fetch_")) + (param i32 i32 (func (param i32 i32)(result i32))) (result i32)) + +(@interface func $fetch (import "" "fetch") + (param string (func (param @statusCode string) (result @returnCode))) + (result @returnCode)) + +(@interface implement (import "" fetch_") + (param $url i32 $len i32 $callb (func (param i32 i32)(result i32))) (result i32) + lcl.get $url + lcl.get $len + memory-to-string "mem2" + lcl.get $callb + func.bind (env $callbk (func (param i32 i32 i32) (result i32))) + (func + (param $status statusCode $text string) (result returnCode) + lcl.get $status + enum-to-int32 @statusCode + lcl.get $text + string-to-memory "mem2" "malloc" + env.get $callbk + call-indirect + int32-to-enum @returnCode + ) + + call $fetch + enum-to-int32 @returnCode +) + +(func (export "malloc") + (param $sze i32) (result i32) + ... +) +``` + +### Adapter + +Combining the export and import functions leads, in the first instance, to: + +``` +(@adapter M1:"fetch" as M2:"fetch" + (param $url i32 $len i32 $callb (func (param i32 i32)(result i32))) (result i32) + lcl.get $url + lcl.get $len + memory-to-string "mem2" + lcl.get $callb + func.bind (env $callbk (func (param i32 i32 i32) (result i32))) + (func + (param $status statusCode $text string) (result returnCode) + lcl.get $status + enum-to-int32 @statusCode + lcl.get $text + string-to-memory "mem2" "malloc" + env.get $callbk + call-indirect + int32-to-enum @returnCode + ) + + let $cb + let $u + + lcl.get $u + string-to-memory "mem1" "malloc" + lcl.get $cb + func.bind (env $ecb (param $status statusCode $text string) (result returnCode)) + (func + (param $status i32 $txt i32 $len i32) + (result i32) + lcl.get $status + int23-to-enum @statusCode + lcl.get $txt + lcl.get $len + memory-to-string "mem1" + env.get $ecb + call-indirect + enum-to-int32 @returnCode + ) + call $fetch_ + int32-to-enum @returnCode + enum-to-int32 @returnCode +) +``` + +Some reordering, (which is hard to sanction if we allow side-affecting +instructions), gives: + +``` +(@adapter M1:"fetch" as M2:"fetch" + (param $url i32 $len i32 $callb (func (param i32 i32)(result i32))) (result i32) + lcl.get $url + lcl.get $len + memory-to-string "mem2" + let $u + + lcl.get $u + string-to-memory "mem1" "malloc" + + lcl.get $callb + func.bind (env $callbk (func (param i32 i32 i32) (result i32))) + (func + (param $status statusCode $text string) (result returnCode) + lcl.get $status + enum-to-int32 @statusCode + lcl.get $text + string-to-memory "mem2" "malloc" + env.get $callbk + call-indirect + int32-to-enum @returnCode + ) + + let $cb + lcl.get $cb + func.bind (env $ecb (param $status statusCode $text string) (result returnCode)) + (func + (param $status i32 $txt i32 $len i32) + (result i32) + lcl.get $status + int23-to-enum @statusCode + lcl.get $txt + lcl.get $len + memory-to-string "mem1" + env.get $ecb + call-indirect + enum-to-int32 @returnCode + ) + call $fetch_ + int32-to-enum @returnCode + enum-to-int32 @returnCode +) + +``` + +Simplifying low-hanging sequences for clarity: + +``` +(@adapter M1:"fetch" as M2:"fetch" + (param $url i32 $len i32 $callb (func (param i32 i32)(result i32))) (result i32) + lcl.get $url + lcl.get $len + string.copy M2:"mem2" M1:"mem1" "malloc" + + lcl.get $callb + func.bind (env $callbk (func (param i32 i32 i32) (result i32))) + (func + (param $status statusCode $text string) (result returnCode) + lcl.get $status + enum-to-int32 @statusCode + lcl.get $text + string-to-memory "mem2" "malloc" + env.get $callbk + call-indirect + int32-to-enum @returnCode + ) + + func.bind (env $ecb (param $status statusCode $text string) (result returnCode)) + (func + (param $status i32 $txt i32 $len i32) + (result i32) + lcl.get $status + int23-to-enum @statusCode + lcl.get $txt + lcl.get $len + memory-to-string "mem1" + env.get $ecb + call-indirect + enum-to-int32 @returnCode + ) + call $fetch_ +) + +``` + +The double binds in a row can be combined by inlining the call to `env.get $ecb`: + +``` +(@adapter M1:"fetch" as M2:"fetch" + (param $url i32 $len i32 $callb (func (param i32 i32)(result i32))) (result i32) + lcl.get $url + lcl.get $len + string.copy M2:"mem2" M1:"mem1" "malloc" + + lcl.get $callb + + func.bind (env $callbk (func (param i32 i32 i32) (result i32))) + (func + (param $status i32 $txt i32 $len i32) + (result i32) + lcl.get $status + int23-to-enum @statusCode + lcl.get $txt + lcl.get $len + memory-to-string "mem1" + + let $status $text + + lcl.get $status + enum-to-int32 @statusCode + lcl.get $text + string-to-memory "mem2" "malloc" + env.get $callbk + call-indirect + int32-to-enum @returnCode + enum-to-int32 @returnCode + ) + call $fetch_ +) + +``` +More reordering and simplification: + +``` +(@adapter M1:"fetch" as M2:"fetch" + (param $url i32 $len i32 $callb (func (param i32 i32)(result i32))) (result i32) + lcl.get $url + lcl.get $len + string.copy M2:"mem2" M1:"mem1" "malloc" + + lcl.get $callb + + func.bind (env $callbk (func (param i32 i32 i32) (result i32))) + (func + (param $status i32 $txt i32 $len i32) + (result i32) + lcl.get $status + + lcl.get $txt + lcl.get $len + string.copy M1:"mem1" M2:"mem2" "malloc" + + env.get $callbk + call-indirect + ) + call $fetch_ +) + +``` + +Which is reasonable code. + +## Pay by Credit Card + +In order to process a payment with a credit card, various items of information +are required: credit card details, amount, merchant bank, and so on. This +example illustrates the use of nominal types and record types in adapter code. + +>Note that we have aggressively simplified the scenario in order to construct an +>illustrative example. + +## Paint a vector of points + +## Colors + +## Directory Listing From 63cde43745a55b39259e53f53464cf9a727cba93 Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Mon, 16 Sep 2019 15:30:26 -0700 Subject: [PATCH 02/23] Added more examples --- proposals/interface-types/scenarios.md | 331 +++++++++++++++++++++++-- 1 file changed, 306 insertions(+), 25 deletions(-) diff --git a/proposals/interface-types/scenarios.md b/proposals/interface-types/scenarios.md index f7860199..3a06489b 100644 --- a/proposals/interface-types/scenarios.md +++ b/proposals/interface-types/scenarios.md @@ -1,7 +1,7 @@ # A suite of adapter scenarios ## Notes -Added let, func.bind, env.get, string.copy instructions. +Added let, func.bind, env.get, field.get, string.copy, create instructions. Changed arg.get to lcl.get because it is clearer when combined with let. @@ -95,7 +95,7 @@ Passing a string and returning the number of unicode code points in it. (@interface func (export "count") (param $str string) (result i32) lcl.get $str - string-to-memory "mem1" "malloc" + string-to-memory $mem1 "malloc" call "count_" ) (func (export "malloc") @@ -131,7 +131,7 @@ Note that the type of the adapter is in terms of wasm core types. lcl.get $ptr lcl.get $len memory-to-string M2:"mem2" - string-to-memory M1:"mem1" M1:"malloc" + string-to-memory M1:$mem1 M1:"malloc" call M2:"count_" ) ``` @@ -141,7 +141,7 @@ which, after collapsing coercion operators, becomes: (param $ptr i32 $len i32) (result i32)) lcl.get $ptr lcl.get $len - string.copy M2:"mem2" M1:"mem1" M1:"malloc" + string.copy M2:"mem2" M1:$mem1 M1:"malloc" call M2:"count_" ) ``` @@ -161,9 +161,9 @@ The `getenv` function builds on `count` by also returning a `string`. (@interface func (export "getenv") (param $str string) (result string) lcl.get $str - string-to-memory "mem1" "malloc" + string-to-memory $mem1 "malloc" call "getenv_" - memory-to-string "mem1" + memory-to-string $mem1 ) (func (export "malloc") (param $sze i32) (result i32) @@ -206,9 +206,9 @@ stored. lcl.get $ptr lcl.get $len memory-to-string M2:"mem2" - string-to-memory M1:"mem1" M1:"malloc" + string-to-memory M1:$mem1 M1:"malloc" call M1:"getenv_" - memory-to-string M1:"mem1" + memory-to-string M1:$mem1 string-to-memory M2:"mem2" M2:"malloc" ) ``` @@ -220,9 +220,9 @@ which, after collapsing coercions, becomes (param $ptr i32 $len i32) (result i32 i32)) lcl.get $ptr lcl.get $len - string.copy M2:"mem2" M1:"mem1" M1:"malloc" + string.copy M2:"mem2" M1:$mem1 M1:"malloc" call M1:"getenv_" - string.copy M1:"mem1" M2:"mem2" M2:"malloc" + string.copy M1:$mem1 M2:"mem2" M2:"malloc" ) ``` @@ -275,7 +275,7 @@ pushed onto the stack. $cb (func (param $status statusCode $text string) (result returnCode))) (result string) lcl.get $u - string-to-memory "mem1" "malloc" + string-to-memory $mem1 "malloc" lcl.get $cb func.bind (env $ecb (param $status statusCode $text string) (result returnCode)) @@ -286,7 +286,7 @@ pushed onto the stack. int23-to-enum @statusCode lcl.get $txt lcl.get $len - memory-to-string "mem1" + memory-to-string $mem1 env.get $ecb call-indirect enum-to-int32 @returnCode @@ -316,7 +316,7 @@ Importing `fetch` implies exporting the function that implements the callback. (param string (func (param @statusCode string) (result @returnCode))) (result @returnCode)) -(@interface implement (import "" fetch_") +(@interface implement (import "" "fetch_") (param $url i32 $len i32 $callb (func (param i32 i32)(result i32))) (result i32) lcl.get $url lcl.get $len @@ -371,7 +371,7 @@ Combining the export and import functions leads, in the first instance, to: let $u lcl.get $u - string-to-memory "mem1" "malloc" + string-to-memory $mem1 "malloc" lcl.get $cb func.bind (env $ecb (param $status statusCode $text string) (result returnCode)) (func @@ -381,7 +381,7 @@ Combining the export and import functions leads, in the first instance, to: int23-to-enum @statusCode lcl.get $txt lcl.get $len - memory-to-string "mem1" + memory-to-string $mem1 env.get $ecb call-indirect enum-to-int32 @returnCode @@ -404,7 +404,7 @@ instructions), gives: let $u lcl.get $u - string-to-memory "mem1" "malloc" + string-to-memory $mem1 "malloc" lcl.get $callb func.bind (env $callbk (func (param i32 i32 i32) (result i32))) @@ -429,7 +429,7 @@ instructions), gives: int23-to-enum @statusCode lcl.get $txt lcl.get $len - memory-to-string "mem1" + memory-to-string $mem1 env.get $ecb call-indirect enum-to-int32 @returnCode @@ -448,7 +448,7 @@ Simplifying low-hanging sequences for clarity: (param $url i32 $len i32 $callb (func (param i32 i32)(result i32))) (result i32) lcl.get $url lcl.get $len - string.copy M2:"mem2" M1:"mem1" "malloc" + string.copy M2:"mem2" M1:$mem1 "malloc" lcl.get $callb func.bind (env $callbk (func (param i32 i32 i32) (result i32))) @@ -471,7 +471,7 @@ Simplifying low-hanging sequences for clarity: int23-to-enum @statusCode lcl.get $txt lcl.get $len - memory-to-string "mem1" + memory-to-string $mem1 env.get $ecb call-indirect enum-to-int32 @returnCode @@ -488,7 +488,7 @@ The double binds in a row can be combined by inlining the call to `env.get $ecb` (param $url i32 $len i32 $callb (func (param i32 i32)(result i32))) (result i32) lcl.get $url lcl.get $len - string.copy M2:"mem2" M1:"mem1" "malloc" + string.copy M2:"mem2" M1:$mem1 "malloc" lcl.get $callb @@ -500,7 +500,7 @@ The double binds in a row can be combined by inlining the call to `env.get $ecb` int23-to-enum @statusCode lcl.get $txt lcl.get $len - memory-to-string "mem1" + memory-to-string $mem1 let $status $text @@ -524,7 +524,7 @@ More reordering and simplification: (param $url i32 $len i32 $callb (func (param i32 i32)(result i32))) (result i32) lcl.get $url lcl.get $len - string.copy M2:"mem2" M1:"mem1" "malloc" + string.copy M2:"mem2" M1:$mem1 "malloc" lcl.get $callb @@ -536,7 +536,7 @@ More reordering and simplification: lcl.get $txt lcl.get $len - string.copy M1:"mem1" M2:"mem2" "malloc" + string.copy M1:$mem1 M2:"mem2" "malloc" env.get $callbk call-indirect @@ -554,11 +554,292 @@ In order to process a payment with a credit card, various items of information are required: credit card details, amount, merchant bank, and so on. This example illustrates the use of nominal types and record types in adapter code. ->Note that we have aggressively simplified the scenario in order to construct an ->illustrative example. +>Note that we have substantially simplified the scenario in order to construct +>an illustrative example. + +The signature of `payWithCard` as an interface type is: + +``` +cc ::= cc{ + ccNo : int64; + name : string; + expires : { + mon : int8; + year : int16 + } + ccv : int16 +} +payWithCard:(card : cc, amount:int64, session:resource) => boolean +``` + +### Export + +In order to handle the incoming information, a record value must be passed -- +including a `string` component -- and a `session` that denotes the bank +connection must be paired with an existing connection resource. The connection +information is realized as an `eqref` (an `anyref` that supports equality). + +In this scenario, we are assuming that the credit card information is passed by +value (each field in a separate argument) to the internal implementation of the +export, and passed by reference to the import. + +``` +(@interface datatype @cc + (record + (ccNo int64) + (name string) + (expires (record + (mon int8) + (year int16) + ) + ) + (ccv int16) + ) +) + +(@interface typealias @connection eqref) + +(memory (export $mem1) 1) + +(func $payWithCard_ (export ("" "payWithCard_")) + (param i64 i32 i32 i16 i16 eqref) (result i32) + +(@interface func (export "payWithCard") + (param $card @cc + $session (resource @connection)) + (result boolean) + lcl.get $card + field.get #cc.ccNo << access ccNo + + lcl.get $card + field.get #cc.name + string-to-memory $mem1 "malloc" + + lcl.get $card + field.get #cc.expires.mon + + lcl.get $card + field.get #cc.expires.year + + lcl.get $cc + field.get #cc.ccv + + lcl.get $session + call $payWithCard_ + int32-to-enum boolean +) + +(func (export "malloc") + (param $sze i32) (result i32) + ... +) +``` + +### Import + +In this example, we assume that the credit details are passed to the import by +reference; i.e., the actual argument representing the credit card information is +a pointer to a block of memory. + +``` +(func $payWithCard_ (import ("" "payWithCard_")) + (param i32 eqref) (result i32) + +(@interface func $payWithCard (import "" "payWithCard") + (param @cc (resource @connection)) + (result boolean)) + +(@interface implement (import "" "payWithCard_") + (param $cc i32 $conn eqref)(result i32) + lcl.get $cc + i64.load #cc.ccNo + + lcl.get $cc + i32.load #cc.name.ptr + + lcl.get $cc + i32.load #cc.name.len + memory-to-string "mem2" + + lcl.get $cc + i16.load #cc.expires.mon + + lcl.get $cc + i16.load #cc.expires.year + + create (record (mon int16) (year int16)) + + lcl.get $cc + i16.load #cc.ccv + + create @cc + + lcl.get $conn + + call $payWithCard + enum-to-int32 boolean +) +``` + +### Adapter + +Combining the import, exports and distributing the arguments, renaming `cc` for +clarity, we initially get: + +``` +(@adapter M1:"payWithCard" as M2:"payWithCard" + (param $cc i32 $conn eqref)(result i32) + + lcl.get $cc + i64.load mem2:#cc.ccNo + + lcl.get $cc + i32.load mem2:#cc.name.ptr + + lcl.get $cc + i32.load mem2:#cc.name.len + memory-to-string "mem2" + + lcl.get $cc + i16.load mem2:#cc.expires.mon + + lcl.get $cc + i16.load mem2:#cc.expires.year + + create (record (mon int16) (year int16)) + + lcl.get $cc + i16.load mem2:#cc.ccv + + create @cc + + lcl.get $conn + let $session + let $card + + lcl.get $card + field.get #cc.ccNo << access ccNo + + lcl.get $card + field.get #cc.name + string-to-memory $mem1 "malloc" + + lcl.get $card + field.get #cc.expires.mon + + lcl.get $card + field.get #cc.expires.year + + lcl.get $cc + field.get #cc.ccv + + lcl.get $session + call $payWithCard_ + int-to-enum boolean + enum-to-int32 boolean +) +``` + +With some assumptions (such as no aliasing, no writing to locals, no +re-entrancy), we can propagate and inline the definitions of intermediates: + +``` +(@adapter M1:"payWithCard" as M2:"payWithCard" + (param $cc i32 $conn eqref)(result i32) + lcl.get $cc + i64.load mem2:#cc.ccNo + + lcl.get $cc + i32.load mem2:#cc.name.ptr + + lcl.get $cc + i32.load mem2:#cc.name.len + string.copy mem2 mem1 "malloc" + + lcl.get $cc + i16.load mem2:#cc.expires.mon + + lcl.get $cc + i16.load mem2:#cc.expires.year + + lcl.get $cc + i16.load mem2:#cc.ccv + + lcl.get $conn + call $payWithCard_ +) +``` ## Paint a vector of points +In this example we look at how sequences are passed into an API. The signature +of `vectorPaint` is assumed to be: + +``` +point ::= pt(int32,int23) +vectorPaint:(sequence pts) => returnCode +``` + +### Export + +It is assumed that the implementation of `vectorPaint_` requires a +memory-allocated array; each entry of which consists of two contiguous `i32` +values. + +The array is modeled as two values: a pointer to its base and the number of +elements in the array. + +``` +(@interface datatype @point + (oneof + (variant "pt" + (tuple i32 i32)))) + +(memory (export "mem1") 1) + +(func $vectorPaint_ (export ("" "vectorPaint_")) + (param i32 i32) (result i32) + +(@interface func (export "vectorPaint") + (param $pts (sequence @point)) + (result @returnCode) + + lcl.get $pts + sequence.count + allocate scale:8 "malloc" + let array + (for $ix $pts.count << for ix in 0..pts.count + lcl.get $pts + sequence.get $ix + field.get #0 + lcl.get $array + lcl.get $ix + i32.index.store #0 + lcl.get $pts + sequence.get $ix + field.get #1 + lcl.get $array + lcl.get $ix + i32.index.store #4 + ) + lcl.get $array + call $vectorPaint_ + int32-to-enum @returnCode +) +``` + ## Colors +Packed values + ## Directory Listing + +Error recovery + + + + + + + + From 91b82830fb5aec84e24232f5bab1f9fb24135656 Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Wed, 18 Sep 2019 10:17:44 -0700 Subject: [PATCH 03/23] Cleaned up some, refactored first example --- proposals/interface-types/scenarios.md | 299 ++++++++++++++++++++----- 1 file changed, 237 insertions(+), 62 deletions(-) diff --git a/proposals/interface-types/scenarios.md b/proposals/interface-types/scenarios.md index 3a06489b..935d0b9a 100644 --- a/proposals/interface-types/scenarios.md +++ b/proposals/interface-types/scenarios.md @@ -13,10 +13,13 @@ Calling a two argument integer function, should result in effectively zero code. ``` (@interface func (export "twizzle") - (param $a1 int32)(param $a2 int32) (result int32) + (param $a1 s32)(param $a2 s32) (result s32) lcl.get $a1 + s32-to-i23 lcl.get $a2 + s32-to-i32 call "twizzle_" + i32-to-s32 ) ``` @@ -24,13 +27,16 @@ Calling a two argument integer function, should result in effectively zero code. ``` (@interface func (import "" "twozzle") - (param $a1 int32)(param $a2 int32) (result int32) + (param $a1 s32)(param $a2 s32) (result s32) ) (@interface implement (import "" "twozzle_") - (param $b1 int32)(param $b2 int32) (result int32) + (param $b1 i32)(param $b2 i32) (result i32) lcl.get $b1 + i32-to-s32 lcl.get $b2 + i32-to-s32 call-import "twozzle" + s32-to-i32 ) ``` @@ -38,51 +44,130 @@ Calling a two argument integer function, should result in effectively zero code. ### Adapter Code ``` -(@adapter M2:"twozzle" as M1:"twizzle" - (param $a1 int32)(param $a2 int32) (result int32) - lcl.get $a1 - lcl.get $a2 +(@adapter implement (import "" "twozzle_") + (param $b1 i32)(param $b2 i32) (result i32) + lcl.get $b1 + lcl.get $b2 call M1:"twizzle_" ) ``` This should be viewed as the result of optimizations over an in-line substitution: ``` -(@adapter M2:"twozzle" as M1:"twizzle" - (param $b1 int32)(param $b2 int32) (result int32) +(@adapter implement (import "" "twozzle_") + (param $b1 i32)(param $b2 i32) (result i32) lcl.get $b1 + i32-to-s32 lcl.get $b2 - let $a1 $a2 - lcl.get $a1 - lcl.get $a2 - call M1:"twizzle_" + i32-to-s32 + let s32 (local $a2 s32) + let s32 (local $a1 s32) + lcl.get $a1 + s32-to-i23 + lcl.get $a2 + s32-to-i32 + call M1:"twizzle_" + i32-to-s32 + end + end + s32-to-i32 +) +``` + +The `let` pseudo instruction pops elements off the stack and gives them names; +and is part of the [function reference proposal] +[https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md#local-bindings]. + +The first step in 'optimising' this sequence is to remove the `let` +instructions, where possible, and replacing instances of references to them with +the sub-sequences that gave the variables their value. + +For example, in + +``` +let s32 (local $a1 s32) +``` +the sub-sequence that results in the value for `$a1` is: + +``` +lcl.get $b1 +i32-to-s32 +``` + +so, removing the `let` for `a2`, and replacing `lcl.get $a2` with its defining +subsequence gives: + +``` +(@adapter implement (import "" "twozzle_") + (param $b1 i32)(param $b2 i32) (result i32) + lcl.get $b1 + i32-to-s32 + lcl.get $b2 + i32-to-s32 + let s32 (local $a2 s32) + lcl.get $b1 + i32-to-s32 + s32-to-i23 + lcl.get $a2 + s32-to-i32 + call M1:"twizzle_" + i32-to-s32 + end + s32-to-i32 +) + +``` +and, removing the redundant pair: + +``` +i32-to-s32 +s32-to-i32 +``` +gives: + +``` +(@adapter implement (import "" "twozzle_") + (param $b1 i32)(param $b2 i32) (result i32) + lcl.get $b2 + i32-to-s32 + let s32 (local $a2 s32) + lcl.get $b1 + lcl.get $a2 + s32-to-i32 + call M1:"twizzle_" + i32-to-s32 + end + s32-to-i32 ) ``` -The `let` pseudo instruction pops elements off the stack and gives them names. -Inlining the above example, eliminating the combination `lcl.get $b2`; `let $a2` by rewriting `$b2` with `$a2`: +Repeating this for the second `let` gives: + ``` -(@adapter M2:"twozzle" as M1:"twizzle" - (param $b1 int32)(param $a2 int32) (result int32) +(@adapter implement (import "" "twozzle_") + (param $b1 i32)(param $b2 i32) (result i32) lcl.get $b1 - let $a1 - lcl.get $a1 - lcl.get $a2 + lcl.get $b2 call M1:"twizzle_" + i32-to-s32 + s32-to-i32 ) ``` -and again for `$b1/$a1` give the result: + +with the final removal of the redundant coercion pair at the end: + ``` -(@adapter M2:"twozzle" as M1:"twizzle" - (param $a1 int32)(param $a2 int32) (result int32) - lcl.get $a1 - lcl.get $a2 +(@adapter implement (import "" "twozzle_") + (param $b1 i32)(param $b2 i32) (result i32) + lcl.get $b1 + lcl.get $b2 call M1:"twizzle_" ) + ``` -Below, we will assume that this transformation is applied automatically; except -where we need to show what happens more clearly. +Below, we will assume that similar transformations are applied automatically; +except where we need to show what happens more clearly. ## Counting Unicodes @@ -289,10 +374,10 @@ pushed onto the stack. memory-to-string $mem1 env.get $ecb call-indirect - enum-to-int32 @returnCode + enum-to-i32 @returnCode ) call $fetch_ - int32-to-enum @returnCode + i32-to-enum @returnCode ) (func (export "malloc") @@ -326,16 +411,16 @@ Importing `fetch` implies exporting the function that implements the callback. (func (param $status statusCode $text string) (result returnCode) lcl.get $status - enum-to-int32 @statusCode + enum-to-i32 @statusCode lcl.get $text string-to-memory "mem2" "malloc" env.get $callbk call-indirect - int32-to-enum @returnCode + i32-to-enum @returnCode ) call $fetch - enum-to-int32 @returnCode + enum-to-i32 @returnCode ) (func (export "malloc") @@ -359,12 +444,12 @@ Combining the export and import functions leads, in the first instance, to: (func (param $status statusCode $text string) (result returnCode) lcl.get $status - enum-to-int32 @statusCode + enum-to-i32 @statusCode lcl.get $text string-to-memory "mem2" "malloc" env.get $callbk call-indirect - int32-to-enum @returnCode + i32-to-enum @returnCode ) let $cb @@ -384,11 +469,11 @@ Combining the export and import functions leads, in the first instance, to: memory-to-string $mem1 env.get $ecb call-indirect - enum-to-int32 @returnCode + enum-to-i32 @returnCode ) call $fetch_ - int32-to-enum @returnCode - enum-to-int32 @returnCode + i32-to-enum @returnCode + enum-to-i32 @returnCode ) ``` @@ -411,12 +496,12 @@ instructions), gives: (func (param $status statusCode $text string) (result returnCode) lcl.get $status - enum-to-int32 @statusCode + enum-to-i32 @statusCode lcl.get $text string-to-memory "mem2" "malloc" env.get $callbk call-indirect - int32-to-enum @returnCode + i32-to-enum @returnCode ) let $cb @@ -432,11 +517,11 @@ instructions), gives: memory-to-string $mem1 env.get $ecb call-indirect - enum-to-int32 @returnCode + enum-to-i32 @returnCode ) call $fetch_ - int32-to-enum @returnCode - enum-to-int32 @returnCode + i32-to-enum @returnCode + enum-to-i32 @returnCode ) ``` @@ -455,12 +540,12 @@ Simplifying low-hanging sequences for clarity: (func (param $status statusCode $text string) (result returnCode) lcl.get $status - enum-to-int32 @statusCode + enum-to-i32 @statusCode lcl.get $text string-to-memory "mem2" "malloc" env.get $callbk call-indirect - int32-to-enum @returnCode + i32-to-enum @returnCode ) func.bind (env $ecb (param $status statusCode $text string) (result returnCode)) @@ -474,7 +559,7 @@ Simplifying low-hanging sequences for clarity: memory-to-string $mem1 env.get $ecb call-indirect - enum-to-int32 @returnCode + enum-to-i32 @returnCode ) call $fetch_ ) @@ -505,13 +590,13 @@ The double binds in a row can be combined by inlining the call to `env.get $ecb` let $status $text lcl.get $status - enum-to-int32 @statusCode + enum-to-i32 @statusCode lcl.get $text string-to-memory "mem2" "malloc" env.get $callbk call-indirect - int32-to-enum @returnCode - enum-to-int32 @returnCode + i32-to-enum @returnCode + enum-to-i32 @returnCode ) call $fetch_ ) @@ -626,7 +711,7 @@ export, and passed by reference to the import. lcl.get $session call $payWithCard_ - int32-to-enum boolean + i32-to-enum boolean ) (func (export "malloc") @@ -677,7 +762,7 @@ a pointer to a block of memory. lcl.get $conn call $payWithCard - enum-to-int32 boolean + enum-to-i32 boolean ) ``` @@ -736,7 +821,7 @@ clarity, we initially get: lcl.get $session call $payWithCard_ int-to-enum boolean - enum-to-int32 boolean + enum-to-i32 boolean ) ``` @@ -776,7 +861,7 @@ In this example we look at how sequences are passed into an API. The signature of `vectorPaint` is assumed to be: ``` -point ::= pt(int32,int23) +point ::= pt(i32,i32) vectorPaint:(sequence pts) => returnCode ``` @@ -789,6 +874,10 @@ values. The array is modeled as two values: a pointer to its base and the number of elements in the array. +The primary pattern with processing sequences is the iterator pattern; which is +modeled by the `for` instruction; which iterates a code fragment over a +sequence. + ``` (@interface datatype @point (oneof @@ -808,15 +897,14 @@ elements in the array. sequence.count allocate scale:8 "malloc" let array - (for $ix $pts.count << for ix in 0..pts.count - lcl.get $pts - sequence.get $ix + lcl.get $pts + (for $ix $pt ; for ix,pt in pts + lcl.get $pt field.get #0 lcl.get $array lcl.get $ix i32.index.store #0 - lcl.get $pts - sequence.get $ix + lcl.get $pt field.get #1 lcl.get $array lcl.get $ix @@ -824,22 +912,109 @@ elements in the array. ) lcl.get $array call $vectorPaint_ - int32-to-enum @returnCode + i32-to-enum @returnCode ) ``` -## Colors +### Import -Packed values +The primary task in passing a vector of values is the construction of a `sequence`. -## Directory Listing +``` +(func $vectorPaint_ (import ("" "vectorPaint_")) + (param i32 i32) (result i32) -Error recovery +(@interface func $vectorPaint (import "" "vectorPaint") + (param @ptr (sequence @point)) + (result @returnCode)) + +(@interface implement (import "" "vectorPaint_") + (param $points i32 $count i32)(result i32) + lcl.get $points + let $ptr + sequence.start @point + lcl.get $count + loop-for $vector + lcl.get $ptr + i32.load #0 + lcl.get $ptr + i32.load #1 + create @point + sequence.append + lcl.inc $ptr #sizeof(point) + lcl.decr $mx + br_if $vector + end + sequence.complete + call $vectorPaint + enum-to-i32 @returnCode +) +``` + +The `sequence.start`, `sequence.append` and `sequence.complete` instructions are +used to manage the generation of sequences. The `loop-for` pseudo instruction +facilitates the adaptation process, but is a slight generalization of the core +wasm `loop` pattern. +### Adapter + +Combining the import and export sequences into an adapter code depends on being +able to fuse the generating loop with the iterating loop. + +The initial in-line version gives: + +``` +(@adapter M1:"vectorPaint" as M2:"vectorPaint" + (param $points i32 $count i32)(result i32) + lcl.get $points + let $ptr + sequence.start @point + lcl.get $count + loop-for $vector + lcl.get $ptr + i32.load #0 + lcl.get $ptr + i32.load #1 + create @point + sequence.append + lcl.inc $ptr #sizeof(point) + lcl.decr $mx + br_if $vector + end + sequence.complete + + let $pts + lcl.get $pts + sequence.count + allocate scale:8 "malloc" + let array + lcl.get $pts + (for $ix $pt ; for ix,pt in pts + lcl.get $pt + field.get #0 + lcl.get $array + lcl.get $ix + i32.index.store #0 + lcl.get $pt + field.get #1 + lcl.get $array + lcl.get $ix + i32.index.store #4 + ) + lcl.get $array + call $vectorPaint_ + i32-to-enum @returnCode + enum-to-i32 @returnCode +) +``` +## Colors +Packed values +## Directory Listing +Error recovery From 144dc8b2c8947f3a225e95447a1cf0258e8fb09e Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Wed, 18 Sep 2019 10:23:38 -0700 Subject: [PATCH 04/23] Fix reference to function references --- proposals/interface-types/scenarios.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/proposals/interface-types/scenarios.md b/proposals/interface-types/scenarios.md index 935d0b9a..caa99406 100644 --- a/proposals/interface-types/scenarios.md +++ b/proposals/interface-types/scenarios.md @@ -1,5 +1,9 @@ # A suite of adapter scenarios +This suite of examples is intended to demonstrate a spanning set of examples and +transformations that permit the use of interface types to specify imports and +exports. + ## Notes Added let, func.bind, env.get, field.get, string.copy, create instructions. @@ -43,6 +47,9 @@ Calling a two argument integer function, should result in effectively zero code. ### Adapter Code +The adapter code, that maps the import of `twozzle_` to its implementation as +`twizzle_` is: + ``` (@adapter implement (import "" "twozzle_") (param $b1 i32)(param $b2 i32) (result i32) @@ -75,8 +82,7 @@ This should be viewed as the result of optimizations over an in-line substitutio ``` The `let` pseudo instruction pops elements off the stack and gives them names; -and is part of the [function reference proposal] -[https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md#local-bindings]. +and is part of the [function reference proposal][https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md#local-bindings]. The first step in 'optimising' this sequence is to remove the `let` instructions, where possible, and replacing instances of references to them with From 550d543c0aa7082a22f7b2f43b341c3131a554e6 Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Wed, 18 Sep 2019 10:25:27 -0700 Subject: [PATCH 05/23] Again --- proposals/interface-types/scenarios.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/interface-types/scenarios.md b/proposals/interface-types/scenarios.md index caa99406..a2d2b459 100644 --- a/proposals/interface-types/scenarios.md +++ b/proposals/interface-types/scenarios.md @@ -82,7 +82,7 @@ This should be viewed as the result of optimizations over an in-line substitutio ``` The `let` pseudo instruction pops elements off the stack and gives them names; -and is part of the [function reference proposal][https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md#local-bindings]. +and is part of the [function reference proposal](https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md#local-bindings). The first step in 'optimising' this sequence is to remove the `let` instructions, where possible, and replacing instances of references to them with From c735e3c76a553fdc0b0cd49c11336e01cd18fa51 Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Thu, 19 Sep 2019 15:12:16 -0700 Subject: [PATCH 06/23] Refactored fetch example --- proposals/interface-types/scenarios.md | 479 +++++++++++-------------- 1 file changed, 206 insertions(+), 273 deletions(-) diff --git a/proposals/interface-types/scenarios.md b/proposals/interface-types/scenarios.md index a2d2b459..3db1bfac 100644 --- a/proposals/interface-types/scenarios.md +++ b/proposals/interface-types/scenarios.md @@ -55,10 +55,13 @@ The adapter code, that maps the import of `twozzle_` to its implementation as (param $b1 i32)(param $b2 i32) (result i32) lcl.get $b1 lcl.get $b2 - call M1:"twizzle_" + call Mx:"twizzle_" ) ``` +>Note: we adopt the convention that the `Mx:` prefix refers to the exporting +>module and `Mi:` refers to the importing module. + This should be viewed as the result of optimizations over an in-line substitution: ``` (@adapter implement (import "" "twozzle_") @@ -73,7 +76,7 @@ This should be viewed as the result of optimizations over an in-line substitutio s32-to-i23 lcl.get $a2 s32-to-i32 - call M1:"twizzle_" + call Mx:"twizzle_" i32-to-s32 end end @@ -82,11 +85,17 @@ This should be viewed as the result of optimizations over an in-line substitutio ``` The `let` pseudo instruction pops elements off the stack and gives them names; -and is part of the [function reference proposal](https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md#local-bindings). +and is part of the [function reference +proposal](https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md#local-bindings). + +The overall goal of the rewriting of adapters is to eliminate references to +interface types and their values. This reflects the intention to construct +executable code that implements the import of functions whose types are +specified using interface types. The first step in 'optimising' this sequence is to remove the `let` instructions, where possible, and replacing instances of references to them with -the sub-sequences that gave the variables their value. +the sub-sequences that gave the bound variables their value. For example, in @@ -116,7 +125,7 @@ subsequence gives: s32-to-i23 lcl.get $a2 s32-to-i32 - call M1:"twizzle_" + call Mx:"twizzle_" i32-to-s32 end s32-to-i32 @@ -140,7 +149,7 @@ gives: lcl.get $b1 lcl.get $a2 s32-to-i32 - call M1:"twizzle_" + call Mx:"twizzle_" i32-to-s32 end s32-to-i32 @@ -154,7 +163,7 @@ Repeating this for the second `let` gives: (param $b1 i32)(param $b2 i32) (result i32) lcl.get $b1 lcl.get $b2 - call M1:"twizzle_" + call Mx:"twizzle_" i32-to-s32 s32-to-i32 ) @@ -167,7 +176,7 @@ with the final removal of the redundant coercion pair at the end: (param $b1 i32)(param $b2 i32) (result i32) lcl.get $b1 lcl.get $b2 - call M1:"twizzle_" + call Mx:"twizzle_" ) ``` @@ -177,18 +186,30 @@ except where we need to show what happens more clearly. ## Counting Unicodes -Passing a string and returning the number of unicode code points in it. +Passing a string and returning the number of unicode code points in it. The +interface type signature for `countCodes` is: + +``` +countCodes:(string)=>u32 +``` ### Export +To implement `countCodes` the incoming `string` must be mapped to the local +linear memory; which, in turn, implies invoking an allocator to find space for +it: + ``` -(memory (export "mem1") 1) -(@interface func (export "count") - (param $str string) (result i32) +(@interface func (export "countCodes") + (param $str string) (result u32) lcl.get $str - string-to-memory $mem1 "malloc" - call "count_" + string-to-memory $memx "malloc" + call "countCodes_" + i32-to-u23 ) + +(memory (export "memx") 1) + (func (export "malloc") (param $sze i32) (result i32) ... @@ -197,65 +218,83 @@ Passing a string and returning the number of unicode code points in it. ### Import +Importing `countCodes` involves reading a `string` out of linear memory. + ``` -(memory (export "mem2" 1) -(func $count_ (import ("" "count_")) +(memory (export "memi" 1) +(func $count_ (import ("" "countCodes_")) (param i32 i32) (result i32)) -(@interface func $count (import "" "count") - (param $str string) (result i32)) -(@interface implement (import "" "count_") + +(@interface func $count (import "" "countCodes") + (param $str string) (result u32)) + +(@interface implement (import "" "countCodes_") (param $ptr i32 $len i32) (result i32)) lcl.get $ptr lcl.get $len - memory-to-string "mem2" - call-import $count + memory-to-string "memi" + call-import "countCodes" + u32-to-i32 ) ``` ### Adapter code -Note that the type of the adapter is in terms of wasm core types. +After inlining and simple local variable binding elimination, we get a pair of +coercion operators that read a string out of one memory and write it into +another: ``` -(@adapter M1:"count" as M2:"count" +(@adapter implement (import "" "countCodes_") (param $ptr i32 $len i32) (result i32)) lcl.get $ptr lcl.get $len - memory-to-string M2:"mem2" - string-to-memory M1:$mem1 M1:"malloc" - call M2:"count_" + memory-to-string Mi:"memi" + string-to-memory Mx:$memx Mx:"malloc" + call Mx:"countCodes_" ) ``` which, after collapsing coercion operators, becomes: ``` -(@adapter M1:"count" as M2:"count" +(@adapter implement (import "" "countCodes_") (param $ptr i32 $len i32) (result i32)) lcl.get $ptr lcl.get $len - string.copy M2:"mem2" M1:$mem1 M1:"malloc" - call M2:"count_" + string.copy Mi:"memi" Mx:"memx" Mx:"malloc" + call Mx:"countCodes_" ) ``` This assumes that `string.copy` combines memory allocation, string copy and returns the new address and repeats the size of the string. +This also assumes that the `malloc` cannot fail; below we look at exception +handling as a way of partially recovering from this failure. Without explicit +exception handling, a failed `malloc` is required to trap. + ## Getenv Function -The `getenv` function builds on `count` by also returning a `string`. +The `getenv` function builds on `countCodes` by also returning a `string`. Its +interface type signature is: + +``` +getenv:(string)=>string +``` ### Export ``` -(memory (export "mem1") 1) (@interface func (export "getenv") (param $str string) (result string) lcl.get $str - string-to-memory $mem1 "malloc" + string-to-memory "memx" "malloc" call "getenv_" - memory-to-string $mem1 + memory-to-string "memx" ) + +(memory (export "memx") 1) + (func (export "malloc") (param $sze i32) (result i32) ... @@ -264,24 +303,27 @@ The `getenv` function builds on `count` by also returning a `string`. ### Import -The importer must also export `malloc` so that the returned string can be +The importer must also export a version of `malloc` so that the returned string can be stored. ``` -(memory (export "mem2" 1) (func $getenv_ (import ("" "getenv_")) (param i32 i32) (result i32 i32)) + (@interface func $getenv (import "" "getenv") (param $str string) (result string)) + (@interface implement (import "" "getenv_") - (param $ptr i32 $len i32) (result i32)) + (param $ptr i32) (param $len i32) (result i32)) lcl.get $ptr lcl.get $len - memory-to-string "mem2" + memory-to-string "memi" call-import $getenv - string-to-memory "mem2" "malloc" + string-to-memory "memi" "malloc" ) +(memory (export "memi" 1) + (func (export "malloc") (param $sze i32) (result i32) ... @@ -291,29 +333,16 @@ stored. ### Adapter -``` -(@adapter M1:"getenv" as M2:"getenv" - (param $ptr i32 $len i32) (result i32 i32)) - lcl.get $ptr - lcl.get $len - memory-to-string M2:"mem2" - string-to-memory M1:$mem1 M1:"malloc" - call M1:"getenv_" - memory-to-string M1:$mem1 - string-to-memory M2:"mem2" M2:"malloc" -) -``` - -which, after collapsing coercions, becomes +After collapsing coercions and inlining variable bindings: ``` -(@adapter M1:"getenv" as M2:"getenv" - (param $ptr i32 $len i32) (result i32 i32)) +(@adapter implement (import "" "getenv_") + (param $ptr i32) (param $len i32) (result i32 i32)) lcl.get $ptr lcl.get $len - string.copy M2:"mem2" M1:$mem1 M1:"malloc" - call M1:"getenv_" - string.copy M1:$mem1 M2:"mem2" M2:"malloc" + string.copy Mi:"memi" Mx:memx Mx:"malloc" + call Mx:"getenv_" + string.copy Mx:"memx" Mi:"memi" Mi:"malloc" ) ``` @@ -322,7 +351,7 @@ which, after collapsing coercions, becomes The `fetch` function includes a callback which is invoked whenever 'something happens' on the session. -It also introduces two new enumeration types: `ReturnCode` and `StatusCode`. +It also introduces two new enumeration types: `returnCode` and `statusCode`. The signature of `fetch` as an interface type is: @@ -359,38 +388,42 @@ pushed onto the stack. ) ) -(memory (export "mem1") 1) +(memory (export "memx") 1) (@interface func (export "fetch") - (param $u string - $cb (func (param $status statusCode $text string) (result returnCode))) + (param $u string) + (param $cb (ref (func (param $status @statusCode) (param $text string) (result @returnCode)))) (result string) lcl.get $u - string-to-memory $mem1 "malloc" + string-to-memory "memx" "malloc" lcl.get $cb - func.bind (env $ecb (param $status statusCode $text string) (result returnCode)) - (func - (param $status i32 $txt i32 $len i32) - (result i32) - lcl.get $status - int23-to-enum @statusCode - lcl.get $txt - lcl.get $len - memory-to-string $mem1 - env.get $ecb - call-indirect - enum-to-i32 @returnCode - ) - call $fetch_ - i32-to-enum @returnCode + func.ref $callBack + func.bind (func (param @statusCode string) (result @returnCode) + call $fetch_ + i32-to-enum @returnCode +) + +(func $callBack + (param $ecb (ref (func (param statusCode string) (result returnCode)))) + (param $status i32) + (param $text i32) + (param $len i32) + (result i32) + lcl.get $status + i32-to-enum @statusCode + lcl.get $text + lcl.get $len + memory-to-string "memx" + lcl.get $ecb + call-indirect + enum-to-i32 @returnCode ) (func (export "malloc") (param $sze i32) (result i32) ... ) - ``` ### Import @@ -398,35 +431,39 @@ pushed onto the stack. Importing `fetch` implies exporting the function that implements the callback. ``` -(memory (export "mem2" 1) +(memory (export "memi" 1) (func $fetch_ (import ("" "fetch_")) - (param i32 i32 (func (param i32 i32)(result i32))) (result i32)) + (param i32 i32 (ref (func (param i32 i32)(result i32)))) (result i32)) (@interface func $fetch (import "" "fetch") - (param string (func (param @statusCode string) (result @returnCode))) + (param string (ref (func (param @statusCode string)) (result @returnCode))) (result @returnCode)) (@interface implement (import "" "fetch_") - (param $url i32 $len i32 $callb (func (param i32 i32)(result i32))) (result i32) + (param $url i32) (param $len i32) (param $callb (ref (func (param i32 i32)(result i32)))) (result i32) lcl.get $url lcl.get $len - memory-to-string "mem2" + memory-to-string "memi" lcl.get $callb - func.bind (env $callbk (func (param i32 i32 i32) (result i32))) - (func - (param $status statusCode $text string) (result returnCode) - lcl.get $status - enum-to-i32 @statusCode - lcl.get $text - string-to-memory "mem2" "malloc" - env.get $callbk - call-indirect - i32-to-enum @returnCode - ) - - call $fetch - enum-to-i32 @returnCode + func.ref $cbEntry + func.bind (func (param i32 i32 i32) (result i32)) + call-import $fetch + enum-to-i32 @returnCode +) + +(func $cbEntry + (param $callBk (ref (func (param i32 i32 i32) (result i32)))) + (param $status @statusCode) + (param $text string) + (result @returnCode) + lcl.get $status + enum-to-i32 @statusCode + lcl.get $text + string-to-memory "memi" "malloc" + lcl.get $callbk + call-indirect + i32-to-enum @returnCode ) (func (export "malloc") @@ -440,46 +477,25 @@ Importing `fetch` implies exporting the function that implements the callback. Combining the export and import functions leads, in the first instance, to: ``` -(@adapter M1:"fetch" as M2:"fetch" - (param $url i32 $len i32 $callb (func (param i32 i32)(result i32))) (result i32) +(@adapter implement (import "" "fetch_") + (param $url i32) (param $len i32) (param $callb (ref (func (param i32 i32)(result i32)))) (result i32) lcl.get $url lcl.get $len - memory-to-string "mem2" + memory-to-string "memi" lcl.get $callb - func.bind (env $callbk (func (param i32 i32 i32) (result i32))) - (func - (param $status statusCode $text string) (result returnCode) - lcl.get $status - enum-to-i32 @statusCode - lcl.get $text - string-to-memory "mem2" "malloc" - env.get $callbk - call-indirect - i32-to-enum @returnCode - ) - - let $cb - let $u - + func.ref $cbEntry + func.bind (func (param i32 i32 i32) (result i32)) + + let ($u string) ($cb (ref (func (param i32 i32 i32) (result i32)))) lcl.get $u - string-to-memory $mem1 "malloc" + string-to-memory "memx" "malloc" + lcl.get $cb - func.bind (env $ecb (param $status statusCode $text string) (result returnCode)) - (func - (param $status i32 $txt i32 $len i32) - (result i32) - lcl.get $status - int23-to-enum @statusCode - lcl.get $txt - lcl.get $len - memory-to-string $mem1 - env.get $ecb - call-indirect - enum-to-i32 @returnCode - ) - call $fetch_ - i32-to-enum @returnCode - enum-to-i32 @returnCode + func.ref Mx:$callBack + func.bind (func (param @statusCode string) (result @returnCode) + call Mx:$fetch_ + i32-to-enum @returnCode + enum-to-i32 @returnCode ) ``` @@ -487,158 +503,72 @@ Some reordering, (which is hard to sanction if we allow side-affecting instructions), gives: ``` -(@adapter M1:"fetch" as M2:"fetch" - (param $url i32 $len i32 $callb (func (param i32 i32)(result i32))) (result i32) +(@adapter implement (import "" "fetch_") + (param $url i32) (param $len i32) (param $callb (ref (func (param i32 i32)(result i32)))) (result i32) lcl.get $url lcl.get $len - memory-to-string "mem2" - let $u + string.copy Mi:"memi" Mx:"memx" Mx:"malloc" - lcl.get $u - string-to-memory $mem1 "malloc" - lcl.get $callb - func.bind (env $callbk (func (param i32 i32 i32) (result i32))) - (func - (param $status statusCode $text string) (result returnCode) - lcl.get $status - enum-to-i32 @statusCode - lcl.get $text - string-to-memory "mem2" "malloc" - env.get $callbk - call-indirect - i32-to-enum @returnCode - ) - - let $cb - lcl.get $cb - func.bind (env $ecb (param $status statusCode $text string) (result returnCode)) - (func - (param $status i32 $txt i32 $len i32) - (result i32) - lcl.get $status - int23-to-enum @statusCode - lcl.get $txt - lcl.get $len - memory-to-string $mem1 - env.get $ecb - call-indirect - enum-to-i32 @returnCode - ) - call $fetch_ - i32-to-enum @returnCode - enum-to-i32 @returnCode + func.ref Mi:$cbEntry + func.bind (func (param i32 i32 i32) (result i32)) + func.ref Mx:$callBack + func.bind (func (param @statusCode string) (result @returnCode) + call Mx:$fetch_ ) ``` +The sequence of a `func.ref` followed by a `func.bind` amounts to a +partial function application and, like regular function application, can be +inlined. -Simplifying low-hanging sequences for clarity: +In particular, we specialize `Mx:$callBack` with the constant `Mi:$cbEntry` +replacing the bound variable `$ecb`. After inlining the now constant function +call, we get ``` -(@adapter M1:"fetch" as M2:"fetch" - (param $url i32 $len i32 $callb (func (param i32 i32)(result i32))) (result i32) - lcl.get $url +(@adapter $callBackx func + (param $callBk (ref (func (param i32 i32 i32) (result i32)))) + (param $status i32) + (param $text i32) + (param $len i32) + (result i32) + lcl.get $status + + lcl.get $text lcl.get $len - string.copy M2:"mem2" M1:$mem1 "malloc" - - lcl.get $callb - func.bind (env $callbk (func (param i32 i32 i32) (result i32))) - (func - (param $status statusCode $text string) (result returnCode) - lcl.get $status - enum-to-i32 @statusCode - lcl.get $text - string-to-memory "mem2" "malloc" - env.get $callbk - call-indirect - i32-to-enum @returnCode - ) - - func.bind (env $ecb (param $status statusCode $text string) (result returnCode)) - (func - (param $status i32 $txt i32 $len i32) - (result i32) - lcl.get $status - int23-to-enum @statusCode - lcl.get $txt - lcl.get $len - memory-to-string $mem1 - env.get $ecb - call-indirect - enum-to-i32 @returnCode - ) - call $fetch_ -) + string.copy Mx:"memx" Mi:"memi" Mi:"malloc" + lcl.get $callbk + call-indirect +) ``` -The double binds in a row can be combined by inlining the call to `env.get $ecb`: +and the original implementation of the adapter becomes: ``` -(@adapter M1:"fetch" as M2:"fetch" - (param $url i32 $len i32 $callb (func (param i32 i32)(result i32))) (result i32) +(@adapter implement (import "" "fetch_") + (param $url i32) + (param $len i32) + (param $callb (ref (func (param i32 i32)(result i32)))) + (result i32) lcl.get $url lcl.get $len - string.copy M2:"mem2" M1:$mem1 "malloc" - - lcl.get $callb - - func.bind (env $callbk (func (param i32 i32 i32) (result i32))) - (func - (param $status i32 $txt i32 $len i32) - (result i32) - lcl.get $status - int23-to-enum @statusCode - lcl.get $txt - lcl.get $len - memory-to-string $mem1 - - let $status $text - - lcl.get $status - enum-to-i32 @statusCode - lcl.get $text - string-to-memory "mem2" "malloc" - env.get $callbk - call-indirect - i32-to-enum @returnCode - enum-to-i32 @returnCode - ) - call $fetch_ -) + string.copy Mi:"memi" Mx:"memx" Mx:"malloc" -``` -More reordering and simplification: - -``` -(@adapter M1:"fetch" as M2:"fetch" - (param $url i32 $len i32 $callb (func (param i32 i32)(result i32))) (result i32) - lcl.get $url - lcl.get $len - string.copy M2:"mem2" M1:$mem1 "malloc" - lcl.get $callb - - func.bind (env $callbk (func (param i32 i32 i32) (result i32))) - (func - (param $status i32 $txt i32 $len i32) - (result i32) - lcl.get $status - - lcl.get $txt - lcl.get $len - string.copy M1:$mem1 M2:"mem2" "malloc" - - env.get $callbk - call-indirect - ) - call $fetch_ + func.ref Mx:$callBackx + func.bind (func (param i32 i32 i32) (result i32)) + call Mx:$fetch_ ) - ``` Which is reasonable code. +>Note that we are not able to specialize `Mx:$callBackx` further because the +>function value passed to `func.bind` is not a known function -- it is part of +>the `fetch` API. + ## Pay by Credit Card In order to process a payment with a credit card, various items of information @@ -652,15 +582,15 @@ The signature of `payWithCard` as an interface type is: ``` cc ::= cc{ - ccNo : int64; + ccNo : u64; name : string; expires : { - mon : int8; - year : int16 + mon : u8; + year : u16 } - ccv : int16 + ccv : u16 } -payWithCard:(card : cc, amount:int64, session:resource) => boolean +payWithCard:(card:cc, amount:s64, session:resource) => boolean ``` ### Export @@ -676,15 +606,15 @@ export, and passed by reference to the import. ``` (@interface datatype @cc - (record - (ccNo int64) + (record "cc" + (ccNo u64) (name string) (expires (record - (mon int8) - (year int16) + (mon u8) + (year u16) ) ) - (ccv int16) + (ccv u16) ) ) @@ -696,8 +626,8 @@ export, and passed by reference to the import. (param i64 i32 i32 i16 i16 eqref) (result i32) (@interface func (export "payWithCard") - (param $card @cc - $session (resource @connection)) + (param $card @cc) + (param $session (resource @connection)) (result boolean) lcl.get $card field.get #cc.ccNo << access ccNo @@ -741,9 +671,12 @@ a pointer to a block of memory. (result boolean)) (@interface implement (import "" "payWithCard_") - (param $cc i32 $conn eqref)(result i32) + (param $cc i32) + (param $conn eqref) + (result i32) lcl.get $cc i64.load #cc.ccNo + i64-to-u64 lcl.get $cc i32.load #cc.name.ptr @@ -758,7 +691,7 @@ a pointer to a block of memory. lcl.get $cc i16.load #cc.expires.year - create (record (mon int16) (year int16)) + create (record (mon u16) (year u16)) lcl.get $cc i16.load #cc.ccv From 898da1455fe3f1530c88b8335148ce23825fe8a5 Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Mon, 23 Sep 2019 16:01:51 -0700 Subject: [PATCH 07/23] Refactored and completed initial version of sequence example --- proposals/interface-types/scenarios.md | 560 ++++++++++++++++--------- 1 file changed, 370 insertions(+), 190 deletions(-) diff --git a/proposals/interface-types/scenarios.md b/proposals/interface-types/scenarios.md index 3db1bfac..2d0b1b73 100644 --- a/proposals/interface-types/scenarios.md +++ b/proposals/interface-types/scenarios.md @@ -7,7 +7,7 @@ exports. ## Notes Added let, func.bind, env.get, field.get, string.copy, create instructions. -Changed arg.get to lcl.get because it is clearer when combined with let. +Changed arg.get to local.get because it is clearer when combined with let. ## Two Argument Integer Function @@ -18,9 +18,9 @@ Calling a two argument integer function, should result in effectively zero code. ``` (@interface func (export "twizzle") (param $a1 s32)(param $a2 s32) (result s32) - lcl.get $a1 + local.get $a1 s32-to-i23 - lcl.get $a2 + local.get $a2 s32-to-i32 call "twizzle_" i32-to-s32 @@ -35,9 +35,9 @@ Calling a two argument integer function, should result in effectively zero code. ) (@interface implement (import "" "twozzle_") (param $b1 i32)(param $b2 i32) (result i32) - lcl.get $b1 + local.get $b1 i32-to-s32 - lcl.get $b2 + local.get $b2 i32-to-s32 call-import "twozzle" s32-to-i32 @@ -53,8 +53,8 @@ The adapter code, that maps the import of `twozzle_` to its implementation as ``` (@adapter implement (import "" "twozzle_") (param $b1 i32)(param $b2 i32) (result i32) - lcl.get $b1 - lcl.get $b2 + local.get $b1 + local.get $b2 call Mx:"twizzle_" ) ``` @@ -66,15 +66,15 @@ This should be viewed as the result of optimizations over an in-line substitutio ``` (@adapter implement (import "" "twozzle_") (param $b1 i32)(param $b2 i32) (result i32) - lcl.get $b1 + local.get $b1 i32-to-s32 - lcl.get $b2 + local.get $b2 i32-to-s32 let s32 (local $a2 s32) let s32 (local $a1 s32) - lcl.get $a1 + local.get $a1 s32-to-i23 - lcl.get $a2 + local.get $a2 s32-to-i32 call Mx:"twizzle_" i32-to-s32 @@ -105,25 +105,25 @@ let s32 (local $a1 s32) the sub-sequence that results in the value for `$a1` is: ``` -lcl.get $b1 +local.get $b1 i32-to-s32 ``` -so, removing the `let` for `a2`, and replacing `lcl.get $a2` with its defining +so, removing the `let` for `a2`, and replacing `local.get $a2` with its defining subsequence gives: ``` (@adapter implement (import "" "twozzle_") (param $b1 i32)(param $b2 i32) (result i32) - lcl.get $b1 + local.get $b1 i32-to-s32 - lcl.get $b2 + local.get $b2 i32-to-s32 let s32 (local $a2 s32) - lcl.get $b1 + local.get $b1 i32-to-s32 s32-to-i23 - lcl.get $a2 + local.get $a2 s32-to-i32 call Mx:"twizzle_" i32-to-s32 @@ -143,11 +143,11 @@ gives: ``` (@adapter implement (import "" "twozzle_") (param $b1 i32)(param $b2 i32) (result i32) - lcl.get $b2 + local.get $b2 i32-to-s32 let s32 (local $a2 s32) - lcl.get $b1 - lcl.get $a2 + local.get $b1 + local.get $a2 s32-to-i32 call Mx:"twizzle_" i32-to-s32 @@ -161,8 +161,8 @@ Repeating this for the second `let` gives: ``` (@adapter implement (import "" "twozzle_") (param $b1 i32)(param $b2 i32) (result i32) - lcl.get $b1 - lcl.get $b2 + local.get $b1 + local.get $b2 call Mx:"twizzle_" i32-to-s32 s32-to-i32 @@ -174,8 +174,8 @@ with the final removal of the redundant coercion pair at the end: ``` (@adapter implement (import "" "twozzle_") (param $b1 i32)(param $b2 i32) (result i32) - lcl.get $b1 - lcl.get $b2 + local.get $b1 + local.get $b2 call Mx:"twizzle_" ) @@ -202,7 +202,7 @@ it: ``` (@interface func (export "countCodes") (param $str string) (result u32) - lcl.get $str + local.get $str string-to-memory $memx "malloc" call "countCodes_" i32-to-u23 @@ -230,8 +230,8 @@ Importing `countCodes` involves reading a `string` out of linear memory. (@interface implement (import "" "countCodes_") (param $ptr i32 $len i32) (result i32)) - lcl.get $ptr - lcl.get $len + local.get $ptr + local.get $len memory-to-string "memi" call-import "countCodes" u32-to-i32 @@ -247,8 +247,8 @@ another: ``` (@adapter implement (import "" "countCodes_") (param $ptr i32 $len i32) (result i32)) - lcl.get $ptr - lcl.get $len + local.get $ptr + local.get $len memory-to-string Mi:"memi" string-to-memory Mx:$memx Mx:"malloc" call Mx:"countCodes_" @@ -258,8 +258,8 @@ which, after collapsing coercion operators, becomes: ``` (@adapter implement (import "" "countCodes_") (param $ptr i32 $len i32) (result i32)) - lcl.get $ptr - lcl.get $len + local.get $ptr + local.get $len string.copy Mi:"memi" Mx:"memx" Mx:"malloc" call Mx:"countCodes_" ) @@ -287,7 +287,7 @@ getenv:(string)=>string ``` (@interface func (export "getenv") (param $str string) (result string) - lcl.get $str + local.get $str string-to-memory "memx" "malloc" call "getenv_" memory-to-string "memx" @@ -315,8 +315,8 @@ stored. (@interface implement (import "" "getenv_") (param $ptr i32) (param $len i32) (result i32)) - lcl.get $ptr - lcl.get $len + local.get $ptr + local.get $len memory-to-string "memi" call-import $getenv string-to-memory "memi" "malloc" @@ -338,8 +338,8 @@ After collapsing coercions and inlining variable bindings: ``` (@adapter implement (import "" "getenv_") (param $ptr i32) (param $len i32) (result i32 i32)) - lcl.get $ptr - lcl.get $len + local.get $ptr + local.get $len string.copy Mi:"memi" Mx:memx Mx:"malloc" call Mx:"getenv_" string.copy Mx:"memx" Mi:"memi" Mi:"malloc" @@ -394,10 +394,10 @@ pushed onto the stack. (param $u string) (param $cb (ref (func (param $status @statusCode) (param $text string) (result @returnCode)))) (result string) - lcl.get $u + local.get $u string-to-memory "memx" "malloc" - lcl.get $cb + local.get $cb func.ref $callBack func.bind (func (param @statusCode string) (result @returnCode) call $fetch_ @@ -410,12 +410,12 @@ pushed onto the stack. (param $text i32) (param $len i32) (result i32) - lcl.get $status + local.get $status i32-to-enum @statusCode - lcl.get $text - lcl.get $len + local.get $text + local.get $len memory-to-string "memx" - lcl.get $ecb + local.get $ecb call-indirect enum-to-i32 @returnCode ) @@ -442,10 +442,10 @@ Importing `fetch` implies exporting the function that implements the callback. (@interface implement (import "" "fetch_") (param $url i32) (param $len i32) (param $callb (ref (func (param i32 i32)(result i32)))) (result i32) - lcl.get $url - lcl.get $len + local.get $url + local.get $len memory-to-string "memi" - lcl.get $callb + local.get $callb func.ref $cbEntry func.bind (func (param i32 i32 i32) (result i32)) call-import $fetch @@ -457,11 +457,11 @@ Importing `fetch` implies exporting the function that implements the callback. (param $status @statusCode) (param $text string) (result @returnCode) - lcl.get $status + local.get $status enum-to-i32 @statusCode - lcl.get $text + local.get $text string-to-memory "memi" "malloc" - lcl.get $callbk + local.get $callbk call-indirect i32-to-enum @returnCode ) @@ -479,18 +479,18 @@ Combining the export and import functions leads, in the first instance, to: ``` (@adapter implement (import "" "fetch_") (param $url i32) (param $len i32) (param $callb (ref (func (param i32 i32)(result i32)))) (result i32) - lcl.get $url - lcl.get $len + local.get $url + local.get $len memory-to-string "memi" - lcl.get $callb + local.get $callb func.ref $cbEntry func.bind (func (param i32 i32 i32) (result i32)) let ($u string) ($cb (ref (func (param i32 i32 i32) (result i32)))) - lcl.get $u + local.get $u string-to-memory "memx" "malloc" - lcl.get $cb + local.get $cb func.ref Mx:$callBack func.bind (func (param @statusCode string) (result @returnCode) call Mx:$fetch_ @@ -505,11 +505,11 @@ instructions), gives: ``` (@adapter implement (import "" "fetch_") (param $url i32) (param $len i32) (param $callb (ref (func (param i32 i32)(result i32)))) (result i32) - lcl.get $url - lcl.get $len + local.get $url + local.get $len string.copy Mi:"memi" Mx:"memx" Mx:"malloc" - lcl.get $callb + local.get $callb func.ref Mi:$cbEntry func.bind (func (param i32 i32 i32) (result i32)) func.ref Mx:$callBack @@ -533,13 +533,13 @@ call, we get (param $text i32) (param $len i32) (result i32) - lcl.get $status + local.get $status - lcl.get $text - lcl.get $len + local.get $text + local.get $len string.copy Mx:"memx" Mi:"memi" Mi:"malloc" - lcl.get $callbk + local.get $callbk call-indirect ) ``` @@ -552,11 +552,11 @@ and the original implementation of the adapter becomes: (param $len i32) (param $callb (ref (func (param i32 i32)(result i32)))) (result i32) - lcl.get $url - lcl.get $len + local.get $url + local.get $len string.copy Mi:"memi" Mx:"memx" Mx:"malloc" - lcl.get $callb + local.get $callb func.ref Mx:$callBackx func.bind (func (param i32 i32 i32) (result i32)) call Mx:$fetch_ @@ -620,32 +620,35 @@ export, and passed by reference to the import. (@interface typealias @connection eqref) -(memory (export $mem1) 1) +(memory (export $memx) 1) (func $payWithCard_ (export ("" "payWithCard_")) - (param i64 i32 i32 i16 i16 eqref) (result i32) + (param i64 i32 i32 i32 i32 i32 eqref) (result i32) (@interface func (export "payWithCard") (param $card @cc) (param $session (resource @connection)) (result boolean) - lcl.get $card + + local.get $card field.get #cc.ccNo << access ccNo + u64-to-i64 - lcl.get $card + local.get $card field.get #cc.name string-to-memory $mem1 "malloc" - lcl.get $card + local.get $card field.get #cc.expires.mon - lcl.get $card + local.get $card field.get #cc.expires.year - lcl.get $cc + local.get $card field.get #cc.ccv - lcl.get $session + local.get $session + resource-to-eqref @connection call $payWithCard_ i32-to-enum boolean ) @@ -674,31 +677,33 @@ a pointer to a block of memory. (param $cc i32) (param $conn eqref) (result i32) - lcl.get $cc - i64.load #cc.ccNo + + local.get $cc + i64.load {offset #cc.ccNo} i64-to-u64 - lcl.get $cc - i32.load #cc.name.ptr + local.get $cc + i32.load {offset #cc.name.ptr} - lcl.get $cc - i32.load #cc.name.len - memory-to-string "mem2" + local.get $cc + i32.load {offset #cc.name.len} + memory-to-string "memi" - lcl.get $cc - i16.load #cc.expires.mon + local.get $cc + i16.load_u {offset #cc.expires.mon} - lcl.get $cc - i16.load #cc.expires.year + local.get $cc + i16.load_u {offset #cc.expires.year} create (record (mon u16) (year u16)) - lcl.get $cc - i16.load #cc.ccv + local.get $cc + i16.load_u {offset #cc.ccv} create @cc - lcl.get $conn + local.get $conn + eqref-to-resource @connection call $payWithCard enum-to-i32 boolean @@ -711,89 +716,111 @@ Combining the import, exports and distributing the arguments, renaming `cc` for clarity, we initially get: ``` -(@adapter M1:"payWithCard" as M2:"payWithCard" - (param $cc i32 $conn eqref)(result i32) +(@adapter implement (import "" "payWithCard_") + (param $cc i32) + (param $conn eqref) + (result i32) - lcl.get $cc - i64.load mem2:#cc.ccNo + local.get $cc + i64.load {offset #cc.ccNo} + i64-to-u64 - lcl.get $cc - i32.load mem2:#cc.name.ptr + local.get $cc + i32.load {offset #cc.name.ptr} - lcl.get $cc - i32.load mem2:#cc.name.len - memory-to-string "mem2" + local.get $cc + i32.load {offset #cc.name.len} + memory-to-string "memi" - lcl.get $cc - i16.load mem2:#cc.expires.mon + local.get $cc + i16.load_u {offset #cc.expires.mon} - lcl.get $cc - i16.load mem2:#cc.expires.year + local.get $cc + i16.load_u {offset #cc.expires.year} - create (record (mon int16) (year int16)) + create (record (mon u16) (year u16)) - lcl.get $cc - i16.load mem2:#cc.ccv + local.get $cc + i16.load_u {offset #cc.ccv} create @cc - lcl.get $conn - let $session - let $card + local.get $conn + eqref-to-resource @connection + + let $session (resource @connection) + let $card @cc - lcl.get $card + local.get $card field.get #cc.ccNo << access ccNo + u64-to-i64 - lcl.get $card + local.get $card field.get #cc.name string-to-memory $mem1 "malloc" - lcl.get $card + local.get $card field.get #cc.expires.mon + u16-to-i32 - lcl.get $card + local.get $card field.get #cc.expires.year + u16-to-i32 - lcl.get $cc + local.get $card field.get #cc.ccv + u16-to-i32 - lcl.get $session + local.get $session + + resource-to-eqref @connection call $payWithCard_ - int-to-enum boolean + i32-to-enum boolean + end + end enum-to-i32 boolean ) ``` With some assumptions (such as no aliasing, no writing to locals, no -re-entrancy), we can propagate and inline the definitions of intermediates: +re-entrancy), we can propagate and inline the definitions of intermediates. This +amounts to 'regular' inlining where we recurse into records and treat the fields +of the record in an analogous fashion to arguments to the call. ``` -(@adapter M1:"payWithCard" as M2:"payWithCard" - (param $cc i32 $conn eqref)(result i32) - lcl.get $cc - i64.load mem2:#cc.ccNo +(@adapter implement (import "" "payWithCard_") + (param $cc i32) + (param $conn eqref) + (result i32) + + local.get $cc + i64.load {offset #cc.ccNo} - lcl.get $cc - i32.load mem2:#cc.name.ptr + local.get $cc + i32.load {offset #cc.name.ptr} - lcl.get $cc - i32.load mem2:#cc.name.len - string.copy mem2 mem1 "malloc" + local.get $cc + i32.load {offset #cc.name.len} + string.copy Mi:"mem2" Mx:"memx" "malloc" - lcl.get $cc - i16.load mem2:#cc.expires.mon + local.get $cc + i16.load_u {offset #cc.expires.mon} - lcl.get $cc - i16.load mem2:#cc.expires.year - - lcl.get $cc - i16.load mem2:#cc.ccv + local.get $cc + i16.load_u {offset #cc.expires.year} + + local.get $cc + i16.load_u {offset #cc.ccv} - lcl.get $conn + local.get $conn call $payWithCard_ ) ``` +There would be different sequences for the adapter if the underlying ABI were different -- +for example, if structured data were passed as a pointer to a local copy for +example. + ## Paint a vector of points In this example we look at how sequences are passed into an API. The signature @@ -823,7 +850,7 @@ sequence. (variant "pt" (tuple i32 i32)))) -(memory (export "mem1") 1) +(memory (export "memx") 1) (func $vectorPaint_ (export ("" "vectorPaint_")) (param i32 i32) (result i32) @@ -832,29 +859,39 @@ sequence. (param $pts (sequence @point)) (result @returnCode) - lcl.get $pts + local.get $pts sequence.count allocate scale:8 "malloc" let array - lcl.get $pts - (for $ix $pt ; for ix,pt in pts - lcl.get $pt - field.get #0 - lcl.get $array - lcl.get $ix - i32.index.store #0 - lcl.get $pt - field.get #1 - lcl.get $array - lcl.get $ix - i32.index.store #4 - ) - lcl.get $array + local.get $pts + sequence.loop $ix << for ix in pts + let $pt + local.get $pt + field.get #0 + s32-to-i32 + local.get $array + local.get $ix + i32.index.store #0 + local.get $pt + field.get #1 + s32-to-i32 + local.get $array + local.get $ix + i32.index.store #4 + end + end + local.get $array call $vectorPaint_ i32-to-enum @returnCode ) ``` +The `sequence.loop` pseudo instruction is used to iterate over a sequence with +two bound variables: an integer index and an element which is bound to +successive elements of the sequence. + +The `sequence.count` instruction returns the number of elements in a sequence. + ### Import The primary task in passing a vector of values is the construction of a `sequence`. @@ -868,21 +905,23 @@ The primary task in passing a vector of values is the construction of a `sequenc (result @returnCode)) (@interface implement (import "" "vectorPaint_") - (param $points i32 $count i32)(result i32) - lcl.get $points + (param $points i32) + (param $count i32) + (result i32) + local.get $points let $ptr sequence.start @point - lcl.get $count - loop-for $vector - lcl.get $ptr + local.get $count + loop-for << loop for $count times + local.get $ptr i32.load #0 - lcl.get $ptr - i32.load #1 + i32-to-s32 + local.get $ptr + i32.load #4 + i32-to-s32 create @point sequence.append - lcl.inc $ptr #sizeof(point) - lcl.decr $mx - br_if $vector + local.incr $ptr #sizeof(point) end sequence.complete call $vectorPaint @@ -891,9 +930,13 @@ The primary task in passing a vector of values is the construction of a `sequenc ``` The `sequence.start`, `sequence.append` and `sequence.complete` instructions are -used to manage the generation of sequences. The `loop-for` pseudo instruction -facilitates the adaptation process, but is a slight generalization of the core -wasm `loop` pattern. +used to manage the generation of sequences. + +The `loop-for` pseudo instruction facilitates the adaptation process, but is a +slight generalization of the core wasm `loop` pattern. + +The `local.incr` and `local.decr` instructions are used to implement the +for-loop iteration. ### Adapter @@ -903,50 +946,187 @@ able to fuse the generating loop with the iterating loop. The initial in-line version gives: ``` -(@adapter M1:"vectorPaint" as M2:"vectorPaint" - (param $points i32 $count i32)(result i32) - lcl.get $points +(@adapter implement (import ("" "vectorPaint_")) + (param $points i32) + (param $count i32) + (result i32) + local.get $points let $ptr sequence.start @point - lcl.get $count - loop-for $vector - lcl.get $ptr + local.get $count + loop-for << loop for $count times + local.get $ptr i32.load #0 - lcl.get $ptr - i32.load #1 + i32-to-s32 + local.get $ptr + i32.load #4 + i32-to-s32 create @point sequence.append - lcl.inc $ptr #sizeof(point) - lcl.decr $mx - br_if $vector + local.incr $ptr #sizeof(point) end sequence.complete - - let $pts - lcl.get $pts - sequence.count - allocate scale:8 "malloc" - let array - lcl.get $pts - (for $ix $pt ; for ix,pt in pts - lcl.get $pt - field.get #0 - lcl.get $array - lcl.get $ix - i32.index.store #0 - lcl.get $pt - field.get #1 - lcl.get $array - lcl.get $ix - i32.index.store #4 - ) - lcl.get $array - call $vectorPaint_ + + let $pts << bind $pts to the sequence + local.get $pts + sequence.count + allocate scale:8 memx:"malloc" + let array + local.get $pts + sequence.loop $ix << for ix in pts + let $pt + local.get $pt + field.get #0 + s32-to-i32 + local.get $array + local.get $ix + i32.index.store memx:#0 + local.get $pt + field.get #1 + s32-to-i32 + local.get $array + local.get $ix + i32.index.store memx:#4 + end + end + end + local.get $array + call mx:$vectorPaint_ i32-to-enum @returnCode enum-to-i32 @returnCode ) ``` +The reasoning for the next loop fusion is that the first loop is generating the +same sequence that the second loop is consuming. So, we fuse the loops by +placing the body of the second loop immediately within the first loop -- after +the construction of individual elements; and eliding the construction of the +sequence itself: + +``` +(@adapter implement (import ("" "vectorPaint_")) + (param $points i32) + (param $count i32) + (result i32) + local.get $points + let $ptr + + local.get $count + allocate scale:8 memx:"malloc" + let array + + local.get $count + loop-for << loop for $count times + local.get $ptr + i32.load #0 + i32-to-s32 + local.get $ptr + i32.load #4 + i32-to-s32 + create @point + + let $pt + local.get $pt + field.get #0 + s32-to-i32 + local.get $array + local.get $ix + i32.index.store memx:#0 + local.get $pt + field.get #1 + s32-to-i32 + local.get $array + local.get $ix + i32.index.store memx:#4 + end + + local.incr $ptr #sizeof(point) + end + + local.get $array + call mx:$vectorPaint_ +) +``` + +Similar reasoning allows us to combine the lifting into an `@point` record and +its lowering: + +``` +(@adapter implement (import ("" "vectorPaint_")) + (param $points i32) + (param $count i32) + (result i32) + local.get $points + let $ptr + + local.get $count + allocate scale:8 memx:"malloc" + let array + + local.get $count + loop-for << loop for $count times + local.get $ptr + i32.load #0 + i32-to-s32 + s32-to-i32 + local.get $array + local.get $ix + i32.index.store memx:#0 + + local.get $ptr + i32.load #4 + i32-to-s32 + s32-to-i32 + local.get $array + local.get $ix + i32.index.store memx:#4 + + local.incr $ptr #sizeof(point) + end + + local.get $array + call mx:$vectorPaint_ +) +``` + +With some final cleanup: + +``` +(@adapter implement (import ("" "vectorPaint_")) + (param $points i32) + (param $count i32) + (result i32) + local.get $points + let $ptr + + local.get $count + allocate scale:8 memx:"malloc" + let array + + local.get $count + loop-for << loop for $count times + local.get $ptr + i32.load #0 + local.get $array + local.get $ix + i32.index.store memx:#0 + + local.get $ptr + i32.load #4 + local.get $array + local.get $ix + i32.index.store memx:#4 + + local.incr $ptr #sizeof(point) + end + + local.get $array + call mx:$vectorPaint_ +) +``` + +Note: The trickiest part of this is actually the handling of the counts. Perhaps +a higher-level set of instructions will help clean this up. ## Colors From 13f556f6dd4e68d5a23d03151c841c48164c11ae Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Tue, 24 Sep 2019 13:40:14 -0700 Subject: [PATCH 08/23] Fix missing $ix refs --- proposals/interface-types/scenarios.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/proposals/interface-types/scenarios.md b/proposals/interface-types/scenarios.md index 2d0b1b73..9aa5979f 100644 --- a/proposals/interface-types/scenarios.md +++ b/proposals/interface-types/scenarios.md @@ -912,7 +912,7 @@ The primary task in passing a vector of values is the construction of a `sequenc let $ptr sequence.start @point local.get $count - loop-for << loop for $count times + loop-for $ix << loop for $count times local.get $ptr i32.load #0 i32-to-s32 @@ -954,7 +954,7 @@ The initial in-line version gives: let $ptr sequence.start @point local.get $count - loop-for << loop for $count times + loop-for $ix << loop for $count times local.get $ptr i32.load #0 i32-to-s32 @@ -1016,7 +1016,7 @@ sequence itself: let array local.get $count - loop-for << loop for $count times + loop-for $ix << loop for $count times local.get $ptr i32.load #0 i32-to-s32 @@ -1064,7 +1064,7 @@ its lowering: let array local.get $count - loop-for << loop for $count times + loop-for $ix << loop for $count times local.get $ptr i32.load #0 i32-to-s32 @@ -1104,7 +1104,7 @@ With some final cleanup: let array local.get $count - loop-for << loop for $count times + loop-for $ix << loop for $count times local.get $ptr i32.load #0 local.get $array From 0916acdd0434e74eb96d79f603c9d35a244c222d Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Mon, 7 Oct 2019 09:20:18 -0700 Subject: [PATCH 09/23] Changed vector of points to use arrays instead of sequences --- proposals/interface-types/scenarios.md | 331 +++++++++++-------------- 1 file changed, 146 insertions(+), 185 deletions(-) diff --git a/proposals/interface-types/scenarios.md b/proposals/interface-types/scenarios.md index 9aa5979f..44e89e83 100644 --- a/proposals/interface-types/scenarios.md +++ b/proposals/interface-types/scenarios.md @@ -631,7 +631,7 @@ export, and passed by reference to the import. (result boolean) local.get $card - field.get #cc.ccNo << access ccNo + field.get #cc.ccNo ;; access ccNo u64-to-i64 local.get $card @@ -752,7 +752,7 @@ clarity, we initially get: let $card @cc local.get $card - field.get #cc.ccNo << access ccNo + field.get #cc.ccNo ;; access ccNo u64-to-i64 local.get $card @@ -828,7 +828,7 @@ of `vectorPaint` is assumed to be: ``` point ::= pt(i32,i32) -vectorPaint:(sequence pts) => returnCode +vectorPaint:(array pts) => returnCode ``` ### Export @@ -840,9 +840,9 @@ values. The array is modeled as two values: a pointer to its base and the number of elements in the array. -The primary pattern with processing sequences is the iterator pattern; which is -modeled by the `for` instruction; which iterates a code fragment over a -sequence. +The primary pattern with processing arrays is the for-loop pattern, together +with array indexing; which is modeled by the `for` instruction; which iterates a +code fragment over a range of numbers. ``` (@interface datatype @point @@ -856,87 +856,91 @@ sequence. (param i32 i32) (result i32) (@interface func (export "vectorPaint") - (param $pts (sequence @point)) + (param $pts (array @point)) (result @returnCode) local.get $pts - sequence.count - allocate scale:8 "malloc" - let array - local.get $pts - sequence.loop $ix << for ix in pts - let $pt - local.get $pt - field.get #0 - s32-to-i32 - local.get $array - local.get $ix - i32.index.store #0 - local.get $pt - field.get #1 - s32-to-i32 - local.get $array - local.get $ix - i32.index.store #4 - end + array-to-memory #8 "malloc" $pt $ix $ptr + local.get $pt + field.get #@point.pt.0 + s32-to-i32 + local.get $ptr + i32.store {offset 0} + local.get $pt + field.get #@point.pt.1 + s32-to-i32 + local.get $ptr + i32.store {offset 4} end - local.get $array call $vectorPaint_ i32-to-enum @returnCode ) ``` -The `sequence.loop` pseudo instruction is used to iterate over a sequence with -two bound variables: an integer index and an element which is bound to -successive elements of the sequence. +The `array-to-memory` block instruction iterates over an array and executes its +block argument for each element of the array. The `array-to-memory` instruction asks the given allocator to +allocate sufficient space in linear memory for the copied out array (given by +multiplying the stride of `#8` by the number of elements in the source array. + +In addition, the `array-to-memory` instruction establishes three local variables +that are in scope for the entire operation: + +* `$pt` which is the element of the array to map + +* `$ptr` the offset within linear memory where the mapped element is located. + +* `$ix` the index of the element to map + +The `array-to-memory` instruction leaves on the stack the offset within linear +memory where the newly allocated struct is. -The `sequence.count` instruction returns the number of elements in a sequence. ### Import -The primary task in passing a vector of values is the construction of a `sequence`. +The primary task in passing a vector of values is the construction of an `array` +to be passed to the imported function. ``` (func $vectorPaint_ (import ("" "vectorPaint_")) (param i32 i32) (result i32) (@interface func $vectorPaint (import "" "vectorPaint") - (param @ptr (sequence @point)) + (param @ptr (array @point)) (result @returnCode)) (@interface implement (import "" "vectorPaint_") (param $points i32) (param $count i32) (result i32) + local.get $points - let $ptr - sequence.start @point local.get $count - loop-for $ix << loop for $count times - local.get $ptr - i32.load #0 + memory-to-array @point #8 $ix $pt + local.get $pt + i32.load {offset 0} i32-to-s32 - local.get $ptr - i32.load #4 + local.get $pt + i32.load {offset 4} i32-to-s32 create @point - sequence.append - local.incr $ptr #sizeof(point) end - sequence.complete call $vectorPaint enum-to-i32 @returnCode ) ``` -The `sequence.start`, `sequence.append` and `sequence.complete` instructions are -used to manage the generation of sequences. +The `memory-to-array` instruction is a higher-order instruction that is used to +create an array from a contiguous region of linear memory. The body of the +instruction is executed once for each element of the array in memory (the two +arguments to the instruction give the memory offset and the count); within the +body of the loop the bound variables `$ix` and `$pt` are the index of the +element and its memory offset respectively. -The `loop-for` pseudo instruction facilitates the adaptation process, but is a -slight generalization of the core wasm `loop` pattern. +The two literal operands of `memory-to-array` are the type of elements of the +constructed array and the stride length of the linear memory array. -The `local.incr` and `local.decr` instructions are used to implement the -for-loop iteration. +The body of the instruction should return an element of the resulting array; and +the instruction itself terminates with the array on the stack. ### Adapter @@ -946,52 +950,39 @@ able to fuse the generating loop with the iterating loop. The initial in-line version gives: ``` -(@adapter implement (import ("" "vectorPaint_")) +(@adapter implement (import "" "vectorPaint_") (param $points i32) (param $count i32) (result i32) + local.get $points - let $ptr - sequence.start @point local.get $count - loop-for $ix << loop for $count times - local.get $ptr - i32.load #0 + memory-to-array @point #8 $ix $pt + local.get $pt + i32.load {offset 0} i32-to-s32 - local.get $ptr - i32.load #4 + local.get $pt + i32.load {offset 4} i32-to-s32 create @point - sequence.append - local.incr $ptr #sizeof(point) end - sequence.complete - - let $pts << bind $pts to the sequence - local.get $pts - sequence.count - allocate scale:8 memx:"malloc" - let array + + let $pts local.get $pts - sequence.loop $ix << for ix in pts - let $pt - local.get $pt - field.get #0 - s32-to-i32 - local.get $array - local.get $ix - i32.index.store memx:#0 - local.get $pt - field.get #1 - s32-to-i32 - local.get $array - local.get $ix - i32.index.store memx:#4 - end + array-to-memory #8 "malloc" $pt $ix $ptr + local.get $pt + field.get #@point.pt.0 + s32-to-i32 + local.get $ptr + i32.store {offset 0} + local.get $pt + field.get #@point.pt.1 + s32-to-i32 + local.get $ptr + i32.store {offset 4} end end - local.get $array - call mx:$vectorPaint_ + call $vectorPaint_ i32-to-enum @returnCode enum-to-i32 @returnCode ) @@ -1001,139 +992,109 @@ The reasoning for the next loop fusion is that the first loop is generating the same sequence that the second loop is consuming. So, we fuse the loops by placing the body of the second loop immediately within the first loop -- after the construction of individual elements; and eliding the construction of the -sequence itself: +array itself. ``` -(@adapter implement (import ("" "vectorPaint_")) +(@adapter implement (import "" "vectorPaint_") (param $points i32) (param $count i32) (result i32) - local.get $points - let $ptr - - local.get $count - allocate scale:8 memx:"malloc" - let array + local.get $count ;; this one is for the eventual call to Mi:$vectorPaint_ local.get $count - loop-for $ix << loop for $count times - local.get $ptr - i32.load #0 - i32-to-s32 - local.get $ptr - i32.load #4 - i32-to-s32 - create @point - - let $pt - local.get $pt - field.get #0 - s32-to-i32 - local.get $array - local.get $ix - i32.index.store memx:#0 - local.get $pt - field.get #1 - s32-to-i32 - local.get $array - local.get $ix - i32.index.store memx:#4 + allocate #8 $arr_ "malloc" + local.get $points + local.get $count + memory.loop #8 $ix memi:$pt_ memx:$ptr_ + local.get $pt_ + i32.load {offset 0} + i32.store {offset 0} + i32.load {offset 4} + i32.store {offset 4} end - - local.incr $ptr #sizeof(point) end - - local.get $array - call mx:$vectorPaint_ + call Mi:$vectorPaint_ ) ``` -Similar reasoning allows us to combine the lifting into an `@point` record and -its lowering: +Note: The trickiest part of this is actually the handling of the counts. In +particular, the rewrite needs to be able to determine the size of the array +before copying starts. + +In some cases, by noticing that the load'n store is effectively a dense copy, +this can be further reduced to: + ``` -(@adapter implement (import ("" "vectorPaint_")) +(@adapter implement (import "" "vectorPaint_") (param $points i32) (param $count i32) (result i32) - local.get $points - let $ptr - - local.get $count - allocate scale:8 memx:"malloc" - let array + local.get $points local.get $count - loop-for $ix << loop for $count times - local.get $ptr - i32.load #0 - i32-to-s32 - s32-to-i32 - local.get $array - local.get $ix - i32.index.store memx:#0 - - local.get $ptr - i32.load #4 - i32-to-s32 - s32-to-i32 - local.get $array - local.get $ix - i32.index.store memx:#4 - - local.incr $ptr #sizeof(point) - end - - local.get $array - call mx:$vectorPaint_ + array.copy #8 memi: memx: mx:malloc + call Mi:$vectorPaint_ ) ``` -With some final cleanup: -``` -(@adapter implement (import ("" "vectorPaint_")) - (param $points i32) - (param $count i32) - (result i32) - local.get $points - let $ptr - - local.get $count - allocate scale:8 memx:"malloc" - let array +## Directory Listing - local.get $count - loop-for $ix << loop for $count times - local.get $ptr - i32.load #0 - local.get $array - local.get $ix - i32.index.store memx:#0 - - local.get $ptr - i32.load #4 - local.get $array - local.get $ix - i32.index.store memx:#4 +In this example, we look as an API to generate a list of files in a directory. - local.incr $ptr #sizeof(point) - end +The interface type signature for this function is: - local.get $array - call mx:$vectorPaint_ -) +``` +listing:(string)=>sequence ``` -Note: The trickiest part of this is actually the handling of the counts. Perhaps -a higher-level set of instructions will help clean this up. +Since the caller does not know how many file names will be returned, it has to +protect itself from potentially abusive situations. That in turn means that the +allocation of the string sequence in the return value may fail. -## Colors +In order to avoid memory leaks, we protect the adapter with exception handling +-- whose purpose it to clean up should an allocation failure occur -Packed values +In addition, memory allocated by the internal implementation of `$listing_` +needs to be released after the successful call. -## Directory Listing +### Export -Error recovery +``` +(memory (export "memx") 1) +(@interface func (export "listing") + (param $dir string) + (result (sequence string)) + + sequence.start string + iterator.start + local.get $dir + string-to-memory "memx" "malloc" + call $it_opendir_ + iterator.for $it_loop + call $it_readdir_ + dup + eqz + br_if $it_loop + memory-to-string "memx" + sequence.append + end + iterator.close + call $it_closedir + sequence.complete + end + end +) + +(func (export "malloc") + (param $sze i32) (result i32) + ... +) + +``` +The `$it_opendir_`, `$it_readdir_` and `$it_closedir_` functions are intended to +denote variants of the standard posix functions that are tailored for use with +the _iterator protocol_ that should form part of this specification. From b1f7373701b1dc36eac1c4e28e0f2cdb15309f76 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Mon, 16 Sep 2019 17:11:11 -0500 Subject: [PATCH 10/23] Mention multi-value and reference-types dependencies --- proposals/interface-types/Explainer.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/proposals/interface-types/Explainer.md b/proposals/interface-types/Explainer.md index c5a90ace..709df1ab 100644 --- a/proposals/interface-types/Explainer.md +++ b/proposals/interface-types/Explainer.md @@ -6,8 +6,9 @@ committing to a single memory representation or sharing scheme. Interface types can only be used in the interfaces of modules and can only be produced or consumed by declarative **interface adapters**. -The proposal is semantically layered on top of the WebAssembly [core spec], -adding only the ability to adapt the imports and exports of a WebAssembly +The proposal is semantically layered on top of the WebAssembly [core spec] +(extended with the [multi-value] and [reference types] proposals), and +adds only the ability to adapt the imports and exports of a WebAssembly module at points which are already host-defined behavior. All adaptations are specified in a [custom section] and this feature can be polyfilled using the [JS API]. @@ -168,8 +169,9 @@ other types and concepts are introduced. ### Export returning string (statically allocated) -Let's start with a WebAssembly module that you can write today that returns a -string that is stored at a fixed location in linear memory: +Let's start with a WebAssembly module that you can write today (with +[multi-value]) that returns a string that is stored at a fixed location in +linear memory: ```wasm (module @@ -689,6 +691,7 @@ leverage existing and planned reference types ([`anyref`], [function references] [Type Import]: https://github.com/WebAssembly/proposal-type-imports/blob/master/proposals/type-imports/Overview.md#imports [GC]: https://github.com/WebAssembly/gc/blob/master/proposals/gc/Overview.md +[Multi-value]: https://github.com/WebAssembly/multi-value/blob/master/proposals/multi-value/Overview.md [Block Validation]: https://webassembly.github.io/multi-value/core/valid/instructions.html#valid-block [Select Validation]: https://webassembly.github.io/reference-types/core/valid/instructions.html#valid-select From 1f7520bb6dbbb19170643cf0e57fbf908e455ee1 Mon Sep 17 00:00:00 2001 From: Jacob Gravelle Date: Thu, 19 Sep 2019 12:27:34 -0700 Subject: [PATCH 11/23] Folder for Working Notes These are notes that are higher-level than a formal spec, but more detailed than makes sense for the Explainer. --- .../working-notes/WorkingNotes.md | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 proposals/interface-types/working-notes/WorkingNotes.md diff --git a/proposals/interface-types/working-notes/WorkingNotes.md b/proposals/interface-types/working-notes/WorkingNotes.md new file mode 100644 index 00000000..9a868829 --- /dev/null +++ b/proposals/interface-types/working-notes/WorkingNotes.md @@ -0,0 +1,23 @@ +# Working Notes + +The purpose of this directory is to hold notes about the current design of the +Interface Types proposal. These notes are intended as analogous to the +[WebAssembly design repo](https://github.com/WebAssembly/design/), as a +less-formal way of designing components of the proposal. Documents should +reflect our current consensus, but the ideas don't need to be fully fleshed out. +These documents should inform the final spec. + +Sample topics: +* How we should handle Wasm exceptions +* How engines might need to optimize combinations of adapters +* What is necessary for MVP, and what can be deferred + +In general, we should try to match the conventions that are already established, +but when inventing some new topic, just making up syntax/instructions is the +right way to go. + +### Q: Why not add these to the Explainer? +These aren't necessarily things that the explainer needs to spell out. If the +purpose of the explainer is to convey the information to a reader from scratch, +the nuances of a given design detail is likely to be distracting detail. It is +likely that some subsets will wind up in the explainer over time. From 642391ad106ed2821c79d46b79fc32cc6ec4cae5 Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Mon, 7 Oct 2019 14:49:20 -0700 Subject: [PATCH 12/23] Refactored export --- proposals/interface-types/scenarios.md | 48 +++++++++++++++++++------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/proposals/interface-types/scenarios.md b/proposals/interface-types/scenarios.md index 44e89e83..63e834bf 100644 --- a/proposals/interface-types/scenarios.md +++ b/proposals/interface-types/scenarios.md @@ -1068,12 +1068,13 @@ needs to be released after the successful call. (param $dir string) (result (sequence string)) - sequence.start string + local.get $dir + string-to-memory "memx" "malloc" + call $it_opendir_ + iterator.start - local.get $dir - string-to-memory "memx" "malloc" - call $it_opendir_ - iterator.for $it_loop + sequence.start string + iterator.while $it_loop call $it_readdir_ dup eqz @@ -1087,14 +1088,37 @@ needs to be released after the successful call. end end ) - -(func (export "malloc") - (param $sze i32) (result i32) - ... -) +... ``` +The `sequence.start`, `sequence.append` and `sequence.complete` instructions are +used to signal the creation of a sequence of values. + +In this particular example, the export adapter is not simply exporting an +individual function but is packaging a combination of three functions that, +together, implement the desired interface. This is an example of a situation +where the C/C++ language is not itself capable of realizing a concept available +in the interface type schema. + The `$it_opendir_`, `$it_readdir_` and `$it_closedir_` functions are intended to -denote variants of the standard posix functions that are tailored for use with -the _iterator protocol_ that should form part of this specification. +denote variants of the standard posix functions that have been slightly tailored +to better fit the scenario. + +The `iterator.start`, `iterator.while` and `iterator.close` instructions model +the equivalent of a `while` loop. The body of the `iterator.start` consists of +three subsections: the initialization phase, an `iterator.while` instruction +which embodies the main part of the iteration and the `iterator.close` whose +body contains instructions that must be performed at the end of the loop. + +The `iterator.while` instruction repeats its internal block until specifically +broken out of; it is effectively equivalent to the normal wasm `loop` +instruction. + +### Import + +Consuming a sequence + +``` + +``` From aa72dca16a72160c39d8598483a294584eade503 Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Mon, 7 Oct 2019 15:08:05 -0700 Subject: [PATCH 13/23] Move scenarios to working notes dir --- proposals/interface-types/{ => working-notes}/scenarios.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename proposals/interface-types/{ => working-notes}/scenarios.md (100%) diff --git a/proposals/interface-types/scenarios.md b/proposals/interface-types/working-notes/scenarios.md similarity index 100% rename from proposals/interface-types/scenarios.md rename to proposals/interface-types/working-notes/scenarios.md From 08ff21d831038419232ffbb5d67fd2bd026095fd Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Mon, 7 Oct 2019 15:18:25 -0700 Subject: [PATCH 14/23] Refactor scenarios into separate dir, one file per. --- .../working-notes/scenarios.md | 1124 ----------------- .../working-notes/scenarios/cc.md | 251 ++++ .../working-notes/scenarios/directory.md | 83 ++ .../working-notes/scenarios/fetch.md | 222 ++++ .../working-notes/scenarios/getenv.md | 73 ++ .../working-notes/scenarios/points.md | 216 ++++ .../working-notes/scenarios/scenarios.md | 18 + .../working-notes/scenarios/towints.md | 174 +++ .../working-notes/scenarios/unicode_count.md | 87 ++ 9 files changed, 1124 insertions(+), 1124 deletions(-) delete mode 100644 proposals/interface-types/working-notes/scenarios.md create mode 100644 proposals/interface-types/working-notes/scenarios/cc.md create mode 100644 proposals/interface-types/working-notes/scenarios/directory.md create mode 100644 proposals/interface-types/working-notes/scenarios/fetch.md create mode 100644 proposals/interface-types/working-notes/scenarios/getenv.md create mode 100644 proposals/interface-types/working-notes/scenarios/points.md create mode 100644 proposals/interface-types/working-notes/scenarios/scenarios.md create mode 100644 proposals/interface-types/working-notes/scenarios/towints.md create mode 100644 proposals/interface-types/working-notes/scenarios/unicode_count.md diff --git a/proposals/interface-types/working-notes/scenarios.md b/proposals/interface-types/working-notes/scenarios.md deleted file mode 100644 index 63e834bf..00000000 --- a/proposals/interface-types/working-notes/scenarios.md +++ /dev/null @@ -1,1124 +0,0 @@ -# A suite of adapter scenarios - -This suite of examples is intended to demonstrate a spanning set of examples and -transformations that permit the use of interface types to specify imports and -exports. - -## Notes -Added let, func.bind, env.get, field.get, string.copy, create instructions. - -Changed arg.get to local.get because it is clearer when combined with let. - -## Two Argument Integer Function - -Calling a two argument integer function, should result in effectively zero code. - -### Export - -``` -(@interface func (export "twizzle") - (param $a1 s32)(param $a2 s32) (result s32) - local.get $a1 - s32-to-i23 - local.get $a2 - s32-to-i32 - call "twizzle_" - i32-to-s32 -) -``` - -### Import - -``` -(@interface func (import "" "twozzle") - (param $a1 s32)(param $a2 s32) (result s32) -) -(@interface implement (import "" "twozzle_") - (param $b1 i32)(param $b2 i32) (result i32) - local.get $b1 - i32-to-s32 - local.get $b2 - i32-to-s32 - call-import "twozzle" - s32-to-i32 -) - -``` - -### Adapter Code - -The adapter code, that maps the import of `twozzle_` to its implementation as -`twizzle_` is: - -``` -(@adapter implement (import "" "twozzle_") - (param $b1 i32)(param $b2 i32) (result i32) - local.get $b1 - local.get $b2 - call Mx:"twizzle_" -) -``` - ->Note: we adopt the convention that the `Mx:` prefix refers to the exporting ->module and `Mi:` refers to the importing module. - -This should be viewed as the result of optimizations over an in-line substitution: -``` -(@adapter implement (import "" "twozzle_") - (param $b1 i32)(param $b2 i32) (result i32) - local.get $b1 - i32-to-s32 - local.get $b2 - i32-to-s32 - let s32 (local $a2 s32) - let s32 (local $a1 s32) - local.get $a1 - s32-to-i23 - local.get $a2 - s32-to-i32 - call Mx:"twizzle_" - i32-to-s32 - end - end - s32-to-i32 -) -``` - -The `let` pseudo instruction pops elements off the stack and gives them names; -and is part of the [function reference -proposal](https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md#local-bindings). - -The overall goal of the rewriting of adapters is to eliminate references to -interface types and their values. This reflects the intention to construct -executable code that implements the import of functions whose types are -specified using interface types. - -The first step in 'optimising' this sequence is to remove the `let` -instructions, where possible, and replacing instances of references to them with -the sub-sequences that gave the bound variables their value. - -For example, in - -``` -let s32 (local $a1 s32) -``` -the sub-sequence that results in the value for `$a1` is: - -``` -local.get $b1 -i32-to-s32 -``` - -so, removing the `let` for `a2`, and replacing `local.get $a2` with its defining -subsequence gives: - -``` -(@adapter implement (import "" "twozzle_") - (param $b1 i32)(param $b2 i32) (result i32) - local.get $b1 - i32-to-s32 - local.get $b2 - i32-to-s32 - let s32 (local $a2 s32) - local.get $b1 - i32-to-s32 - s32-to-i23 - local.get $a2 - s32-to-i32 - call Mx:"twizzle_" - i32-to-s32 - end - s32-to-i32 -) - -``` -and, removing the redundant pair: - -``` -i32-to-s32 -s32-to-i32 -``` -gives: - -``` -(@adapter implement (import "" "twozzle_") - (param $b1 i32)(param $b2 i32) (result i32) - local.get $b2 - i32-to-s32 - let s32 (local $a2 s32) - local.get $b1 - local.get $a2 - s32-to-i32 - call Mx:"twizzle_" - i32-to-s32 - end - s32-to-i32 -) -``` - -Repeating this for the second `let` gives: - -``` -(@adapter implement (import "" "twozzle_") - (param $b1 i32)(param $b2 i32) (result i32) - local.get $b1 - local.get $b2 - call Mx:"twizzle_" - i32-to-s32 - s32-to-i32 -) -``` - -with the final removal of the redundant coercion pair at the end: - -``` -(@adapter implement (import "" "twozzle_") - (param $b1 i32)(param $b2 i32) (result i32) - local.get $b1 - local.get $b2 - call Mx:"twizzle_" -) - -``` - -Below, we will assume that similar transformations are applied automatically; -except where we need to show what happens more clearly. - -## Counting Unicodes - -Passing a string and returning the number of unicode code points in it. The -interface type signature for `countCodes` is: - -``` -countCodes:(string)=>u32 -``` - -### Export - -To implement `countCodes` the incoming `string` must be mapped to the local -linear memory; which, in turn, implies invoking an allocator to find space for -it: - -``` -(@interface func (export "countCodes") - (param $str string) (result u32) - local.get $str - string-to-memory $memx "malloc" - call "countCodes_" - i32-to-u23 -) - -(memory (export "memx") 1) - -(func (export "malloc") - (param $sze i32) (result i32) - ... -) -``` - -### Import - -Importing `countCodes` involves reading a `string` out of linear memory. - -``` -(memory (export "memi" 1) -(func $count_ (import ("" "countCodes_")) - (param i32 i32) (result i32)) - -(@interface func $count (import "" "countCodes") - (param $str string) (result u32)) - -(@interface implement (import "" "countCodes_") - (param $ptr i32 $len i32) (result i32)) - local.get $ptr - local.get $len - memory-to-string "memi" - call-import "countCodes" - u32-to-i32 -) -``` - -### Adapter code - -After inlining and simple local variable binding elimination, we get a pair of -coercion operators that read a string out of one memory and write it into -another: - -``` -(@adapter implement (import "" "countCodes_") - (param $ptr i32 $len i32) (result i32)) - local.get $ptr - local.get $len - memory-to-string Mi:"memi" - string-to-memory Mx:$memx Mx:"malloc" - call Mx:"countCodes_" -) -``` -which, after collapsing coercion operators, becomes: -``` -(@adapter implement (import "" "countCodes_") - (param $ptr i32 $len i32) (result i32)) - local.get $ptr - local.get $len - string.copy Mi:"memi" Mx:"memx" Mx:"malloc" - call Mx:"countCodes_" -) -``` - -This assumes that `string.copy` combines memory allocation, string copy and -returns the new address and repeats the size of the string. - -This also assumes that the `malloc` cannot fail; below we look at exception -handling as a way of partially recovering from this failure. Without explicit -exception handling, a failed `malloc` is required to trap. - -## Getenv Function - -The `getenv` function builds on `countCodes` by also returning a `string`. Its -interface type signature is: - -``` -getenv:(string)=>string -``` - -### Export - - -``` -(@interface func (export "getenv") - (param $str string) (result string) - local.get $str - string-to-memory "memx" "malloc" - call "getenv_" - memory-to-string "memx" -) - -(memory (export "memx") 1) - -(func (export "malloc") - (param $sze i32) (result i32) - ... -) -``` - -### Import - -The importer must also export a version of `malloc` so that the returned string can be -stored. - -``` -(func $getenv_ (import ("" "getenv_")) - (param i32 i32) (result i32 i32)) - -(@interface func $getenv (import "" "getenv") - (param $str string) (result string)) - -(@interface implement (import "" "getenv_") - (param $ptr i32) (param $len i32) (result i32)) - local.get $ptr - local.get $len - memory-to-string "memi" - call-import $getenv - string-to-memory "memi" "malloc" -) - -(memory (export "memi" 1) - -(func (export "malloc") - (param $sze i32) (result i32) - ... -) -``` - - -### Adapter - -After collapsing coercions and inlining variable bindings: - -``` -(@adapter implement (import "" "getenv_") - (param $ptr i32) (param $len i32) (result i32 i32)) - local.get $ptr - local.get $len - string.copy Mi:"memi" Mx:memx Mx:"malloc" - call Mx:"getenv_" - string.copy Mx:"memx" Mi:"memi" Mi:"malloc" -) -``` - -## Fetch Url - -The `fetch` function includes a callback which is invoked whenever 'something -happens' on the session. - -It also introduces two new enumeration types: `returnCode` and `statusCode`. - -The signature of `fetch` as an interface type is: - -``` -fetch:(string,(statusCode,string)=>returnCode)=>returnCode -``` - -The callback requires an inline function definition, which is then bound to a -new closure. - -The `func.bind` adapter instruction takes two literal arguments: a type -specification of the _capture list_ (which is structured in a way that is -analogous to the param section of a function) and a literal function definition. - -The `func.bind` instruction also 'pops off' the stack as many values as are -specified in the capture signature; these are bound into a closure which is -pushed onto the stack. - -### Export - -``` -(@interface datatype @returnCode - (oneof - (enum "ok") - (enum "bad") - ) -) - -(@interface datatype @statusCode - (oneof - (enum "fail") - (enum "havedata") - (enum "eof") - ) -) - -(memory (export "memx") 1) - -(@interface func (export "fetch") - (param $u string) - (param $cb (ref (func (param $status @statusCode) (param $text string) (result @returnCode)))) - (result string) - local.get $u - string-to-memory "memx" "malloc" - - local.get $cb - func.ref $callBack - func.bind (func (param @statusCode string) (result @returnCode) - call $fetch_ - i32-to-enum @returnCode -) - -(func $callBack - (param $ecb (ref (func (param statusCode string) (result returnCode)))) - (param $status i32) - (param $text i32) - (param $len i32) - (result i32) - local.get $status - i32-to-enum @statusCode - local.get $text - local.get $len - memory-to-string "memx" - local.get $ecb - call-indirect - enum-to-i32 @returnCode -) - -(func (export "malloc") - (param $sze i32) (result i32) - ... -) -``` - -### Import - -Importing `fetch` implies exporting the function that implements the callback. - -``` -(memory (export "memi" 1) - -(func $fetch_ (import ("" "fetch_")) - (param i32 i32 (ref (func (param i32 i32)(result i32)))) (result i32)) - -(@interface func $fetch (import "" "fetch") - (param string (ref (func (param @statusCode string)) (result @returnCode))) - (result @returnCode)) - -(@interface implement (import "" "fetch_") - (param $url i32) (param $len i32) (param $callb (ref (func (param i32 i32)(result i32)))) (result i32) - local.get $url - local.get $len - memory-to-string "memi" - local.get $callb - func.ref $cbEntry - func.bind (func (param i32 i32 i32) (result i32)) - call-import $fetch - enum-to-i32 @returnCode -) - -(func $cbEntry - (param $callBk (ref (func (param i32 i32 i32) (result i32)))) - (param $status @statusCode) - (param $text string) - (result @returnCode) - local.get $status - enum-to-i32 @statusCode - local.get $text - string-to-memory "memi" "malloc" - local.get $callbk - call-indirect - i32-to-enum @returnCode -) - -(func (export "malloc") - (param $sze i32) (result i32) - ... -) -``` - -### Adapter - -Combining the export and import functions leads, in the first instance, to: - -``` -(@adapter implement (import "" "fetch_") - (param $url i32) (param $len i32) (param $callb (ref (func (param i32 i32)(result i32)))) (result i32) - local.get $url - local.get $len - memory-to-string "memi" - local.get $callb - func.ref $cbEntry - func.bind (func (param i32 i32 i32) (result i32)) - - let ($u string) ($cb (ref (func (param i32 i32 i32) (result i32)))) - local.get $u - string-to-memory "memx" "malloc" - - local.get $cb - func.ref Mx:$callBack - func.bind (func (param @statusCode string) (result @returnCode) - call Mx:$fetch_ - i32-to-enum @returnCode - enum-to-i32 @returnCode -) -``` - -Some reordering, (which is hard to sanction if we allow side-affecting -instructions), gives: - -``` -(@adapter implement (import "" "fetch_") - (param $url i32) (param $len i32) (param $callb (ref (func (param i32 i32)(result i32)))) (result i32) - local.get $url - local.get $len - string.copy Mi:"memi" Mx:"memx" Mx:"malloc" - - local.get $callb - func.ref Mi:$cbEntry - func.bind (func (param i32 i32 i32) (result i32)) - func.ref Mx:$callBack - func.bind (func (param @statusCode string) (result @returnCode) - call Mx:$fetch_ -) - -``` -The sequence of a `func.ref` followed by a `func.bind` amounts to a -partial function application and, like regular function application, can be -inlined. - -In particular, we specialize `Mx:$callBack` with the constant `Mi:$cbEntry` -replacing the bound variable `$ecb`. After inlining the now constant function -call, we get - -``` -(@adapter $callBackx func - (param $callBk (ref (func (param i32 i32 i32) (result i32)))) - (param $status i32) - (param $text i32) - (param $len i32) - (result i32) - local.get $status - - local.get $text - local.get $len - string.copy Mx:"memx" Mi:"memi" Mi:"malloc" - - local.get $callbk - call-indirect -) -``` - -and the original implementation of the adapter becomes: - -``` -(@adapter implement (import "" "fetch_") - (param $url i32) - (param $len i32) - (param $callb (ref (func (param i32 i32)(result i32)))) - (result i32) - local.get $url - local.get $len - string.copy Mi:"memi" Mx:"memx" Mx:"malloc" - - local.get $callb - func.ref Mx:$callBackx - func.bind (func (param i32 i32 i32) (result i32)) - call Mx:$fetch_ -) -``` - -Which is reasonable code. - ->Note that we are not able to specialize `Mx:$callBackx` further because the ->function value passed to `func.bind` is not a known function -- it is part of ->the `fetch` API. - -## Pay by Credit Card - -In order to process a payment with a credit card, various items of information -are required: credit card details, amount, merchant bank, and so on. This -example illustrates the use of nominal types and record types in adapter code. - ->Note that we have substantially simplified the scenario in order to construct ->an illustrative example. - -The signature of `payWithCard` as an interface type is: - -``` -cc ::= cc{ - ccNo : u64; - name : string; - expires : { - mon : u8; - year : u16 - } - ccv : u16 -} -payWithCard:(card:cc, amount:s64, session:resource) => boolean -``` - -### Export - -In order to handle the incoming information, a record value must be passed -- -including a `string` component -- and a `session` that denotes the bank -connection must be paired with an existing connection resource. The connection -information is realized as an `eqref` (an `anyref` that supports equality). - -In this scenario, we are assuming that the credit card information is passed by -value (each field in a separate argument) to the internal implementation of the -export, and passed by reference to the import. - -``` -(@interface datatype @cc - (record "cc" - (ccNo u64) - (name string) - (expires (record - (mon u8) - (year u16) - ) - ) - (ccv u16) - ) -) - -(@interface typealias @connection eqref) - -(memory (export $memx) 1) - -(func $payWithCard_ (export ("" "payWithCard_")) - (param i64 i32 i32 i32 i32 i32 eqref) (result i32) - -(@interface func (export "payWithCard") - (param $card @cc) - (param $session (resource @connection)) - (result boolean) - - local.get $card - field.get #cc.ccNo ;; access ccNo - u64-to-i64 - - local.get $card - field.get #cc.name - string-to-memory $mem1 "malloc" - - local.get $card - field.get #cc.expires.mon - - local.get $card - field.get #cc.expires.year - - local.get $card - field.get #cc.ccv - - local.get $session - resource-to-eqref @connection - call $payWithCard_ - i32-to-enum boolean -) - -(func (export "malloc") - (param $sze i32) (result i32) - ... -) -``` - -### Import - -In this example, we assume that the credit details are passed to the import by -reference; i.e., the actual argument representing the credit card information is -a pointer to a block of memory. - -``` -(func $payWithCard_ (import ("" "payWithCard_")) - (param i32 eqref) (result i32) - -(@interface func $payWithCard (import "" "payWithCard") - (param @cc (resource @connection)) - (result boolean)) - -(@interface implement (import "" "payWithCard_") - (param $cc i32) - (param $conn eqref) - (result i32) - - local.get $cc - i64.load {offset #cc.ccNo} - i64-to-u64 - - local.get $cc - i32.load {offset #cc.name.ptr} - - local.get $cc - i32.load {offset #cc.name.len} - memory-to-string "memi" - - local.get $cc - i16.load_u {offset #cc.expires.mon} - - local.get $cc - i16.load_u {offset #cc.expires.year} - - create (record (mon u16) (year u16)) - - local.get $cc - i16.load_u {offset #cc.ccv} - - create @cc - - local.get $conn - eqref-to-resource @connection - - call $payWithCard - enum-to-i32 boolean -) -``` - -### Adapter - -Combining the import, exports and distributing the arguments, renaming `cc` for -clarity, we initially get: - -``` -(@adapter implement (import "" "payWithCard_") - (param $cc i32) - (param $conn eqref) - (result i32) - - local.get $cc - i64.load {offset #cc.ccNo} - i64-to-u64 - - local.get $cc - i32.load {offset #cc.name.ptr} - - local.get $cc - i32.load {offset #cc.name.len} - memory-to-string "memi" - - local.get $cc - i16.load_u {offset #cc.expires.mon} - - local.get $cc - i16.load_u {offset #cc.expires.year} - - create (record (mon u16) (year u16)) - - local.get $cc - i16.load_u {offset #cc.ccv} - - create @cc - - local.get $conn - eqref-to-resource @connection - - let $session (resource @connection) - let $card @cc - - local.get $card - field.get #cc.ccNo ;; access ccNo - u64-to-i64 - - local.get $card - field.get #cc.name - string-to-memory $mem1 "malloc" - - local.get $card - field.get #cc.expires.mon - u16-to-i32 - - local.get $card - field.get #cc.expires.year - u16-to-i32 - - local.get $card - field.get #cc.ccv - u16-to-i32 - - local.get $session - - resource-to-eqref @connection - call $payWithCard_ - i32-to-enum boolean - end - end - enum-to-i32 boolean -) -``` - -With some assumptions (such as no aliasing, no writing to locals, no -re-entrancy), we can propagate and inline the definitions of intermediates. This -amounts to 'regular' inlining where we recurse into records and treat the fields -of the record in an analogous fashion to arguments to the call. - -``` -(@adapter implement (import "" "payWithCard_") - (param $cc i32) - (param $conn eqref) - (result i32) - - local.get $cc - i64.load {offset #cc.ccNo} - - local.get $cc - i32.load {offset #cc.name.ptr} - - local.get $cc - i32.load {offset #cc.name.len} - string.copy Mi:"mem2" Mx:"memx" "malloc" - - local.get $cc - i16.load_u {offset #cc.expires.mon} - - local.get $cc - i16.load_u {offset #cc.expires.year} - - local.get $cc - i16.load_u {offset #cc.ccv} - - local.get $conn - call $payWithCard_ -) -``` - -There would be different sequences for the adapter if the underlying ABI were different -- -for example, if structured data were passed as a pointer to a local copy for -example. - -## Paint a vector of points - -In this example we look at how sequences are passed into an API. The signature -of `vectorPaint` is assumed to be: - -``` -point ::= pt(i32,i32) -vectorPaint:(array pts) => returnCode -``` - -### Export - -It is assumed that the implementation of `vectorPaint_` requires a -memory-allocated array; each entry of which consists of two contiguous `i32` -values. - -The array is modeled as two values: a pointer to its base and the number of -elements in the array. - -The primary pattern with processing arrays is the for-loop pattern, together -with array indexing; which is modeled by the `for` instruction; which iterates a -code fragment over a range of numbers. - -``` -(@interface datatype @point - (oneof - (variant "pt" - (tuple i32 i32)))) - -(memory (export "memx") 1) - -(func $vectorPaint_ (export ("" "vectorPaint_")) - (param i32 i32) (result i32) - -(@interface func (export "vectorPaint") - (param $pts (array @point)) - (result @returnCode) - - local.get $pts - array-to-memory #8 "malloc" $pt $ix $ptr - local.get $pt - field.get #@point.pt.0 - s32-to-i32 - local.get $ptr - i32.store {offset 0} - local.get $pt - field.get #@point.pt.1 - s32-to-i32 - local.get $ptr - i32.store {offset 4} - end - call $vectorPaint_ - i32-to-enum @returnCode -) -``` - -The `array-to-memory` block instruction iterates over an array and executes its -block argument for each element of the array. The `array-to-memory` instruction asks the given allocator to -allocate sufficient space in linear memory for the copied out array (given by -multiplying the stride of `#8` by the number of elements in the source array. - -In addition, the `array-to-memory` instruction establishes three local variables -that are in scope for the entire operation: - -* `$pt` which is the element of the array to map - -* `$ptr` the offset within linear memory where the mapped element is located. - -* `$ix` the index of the element to map - -The `array-to-memory` instruction leaves on the stack the offset within linear -memory where the newly allocated struct is. - - -### Import - -The primary task in passing a vector of values is the construction of an `array` -to be passed to the imported function. - -``` -(func $vectorPaint_ (import ("" "vectorPaint_")) - (param i32 i32) (result i32) - -(@interface func $vectorPaint (import "" "vectorPaint") - (param @ptr (array @point)) - (result @returnCode)) - -(@interface implement (import "" "vectorPaint_") - (param $points i32) - (param $count i32) - (result i32) - - local.get $points - local.get $count - memory-to-array @point #8 $ix $pt - local.get $pt - i32.load {offset 0} - i32-to-s32 - local.get $pt - i32.load {offset 4} - i32-to-s32 - create @point - end - call $vectorPaint - enum-to-i32 @returnCode -) -``` - -The `memory-to-array` instruction is a higher-order instruction that is used to -create an array from a contiguous region of linear memory. The body of the -instruction is executed once for each element of the array in memory (the two -arguments to the instruction give the memory offset and the count); within the -body of the loop the bound variables `$ix` and `$pt` are the index of the -element and its memory offset respectively. - -The two literal operands of `memory-to-array` are the type of elements of the -constructed array and the stride length of the linear memory array. - -The body of the instruction should return an element of the resulting array; and -the instruction itself terminates with the array on the stack. - -### Adapter - -Combining the import and export sequences into an adapter code depends on being -able to fuse the generating loop with the iterating loop. - -The initial in-line version gives: - -``` -(@adapter implement (import "" "vectorPaint_") - (param $points i32) - (param $count i32) - (result i32) - - local.get $points - local.get $count - memory-to-array @point #8 $ix $pt - local.get $pt - i32.load {offset 0} - i32-to-s32 - local.get $pt - i32.load {offset 4} - i32-to-s32 - create @point - end - - let $pts - local.get $pts - array-to-memory #8 "malloc" $pt $ix $ptr - local.get $pt - field.get #@point.pt.0 - s32-to-i32 - local.get $ptr - i32.store {offset 0} - local.get $pt - field.get #@point.pt.1 - s32-to-i32 - local.get $ptr - i32.store {offset 4} - end - end - call $vectorPaint_ - i32-to-enum @returnCode - enum-to-i32 @returnCode -) -``` - -The reasoning for the next loop fusion is that the first loop is generating the -same sequence that the second loop is consuming. So, we fuse the loops by -placing the body of the second loop immediately within the first loop -- after -the construction of individual elements; and eliding the construction of the -array itself. - -``` -(@adapter implement (import "" "vectorPaint_") - (param $points i32) - (param $count i32) - (result i32) - - local.get $count ;; this one is for the eventual call to Mi:$vectorPaint_ - local.get $count - allocate #8 $arr_ "malloc" - local.get $points - local.get $count - memory.loop #8 $ix memi:$pt_ memx:$ptr_ - local.get $pt_ - i32.load {offset 0} - i32.store {offset 0} - i32.load {offset 4} - i32.store {offset 4} - end - end - call Mi:$vectorPaint_ -) -``` - -Note: The trickiest part of this is actually the handling of the counts. In -particular, the rewrite needs to be able to determine the size of the array -before copying starts. - -In some cases, by noticing that the load'n store is effectively a dense copy, -this can be further reduced to: - - -``` -(@adapter implement (import "" "vectorPaint_") - (param $points i32) - (param $count i32) - (result i32) - - local.get $points - local.get $count - array.copy #8 memi: memx: mx:malloc - call Mi:$vectorPaint_ -) -``` - - -## Directory Listing - -In this example, we look as an API to generate a list of files in a directory. - -The interface type signature for this function is: - -``` -listing:(string)=>sequence -``` - -Since the caller does not know how many file names will be returned, it has to -protect itself from potentially abusive situations. That in turn means that the -allocation of the string sequence in the return value may fail. - -In order to avoid memory leaks, we protect the adapter with exception handling --- whose purpose it to clean up should an allocation failure occur - -In addition, memory allocated by the internal implementation of `$listing_` -needs to be released after the successful call. - -### Export - -``` -(memory (export "memx") 1) - -(@interface func (export "listing") - (param $dir string) - (result (sequence string)) - - local.get $dir - string-to-memory "memx" "malloc" - call $it_opendir_ - - iterator.start - sequence.start string - iterator.while $it_loop - call $it_readdir_ - dup - eqz - br_if $it_loop - memory-to-string "memx" - sequence.append - end - iterator.close - call $it_closedir - sequence.complete - end - end -) -... - -``` - -The `sequence.start`, `sequence.append` and `sequence.complete` instructions are -used to signal the creation of a sequence of values. - -In this particular example, the export adapter is not simply exporting an -individual function but is packaging a combination of three functions that, -together, implement the desired interface. This is an example of a situation -where the C/C++ language is not itself capable of realizing a concept available -in the interface type schema. - -The `$it_opendir_`, `$it_readdir_` and `$it_closedir_` functions are intended to -denote variants of the standard posix functions that have been slightly tailored -to better fit the scenario. - -The `iterator.start`, `iterator.while` and `iterator.close` instructions model -the equivalent of a `while` loop. The body of the `iterator.start` consists of -three subsections: the initialization phase, an `iterator.while` instruction -which embodies the main part of the iteration and the `iterator.close` whose -body contains instructions that must be performed at the end of the loop. - -The `iterator.while` instruction repeats its internal block until specifically -broken out of; it is effectively equivalent to the normal wasm `loop` -instruction. - -### Import - -Consuming a sequence - -``` - -``` diff --git a/proposals/interface-types/working-notes/scenarios/cc.md b/proposals/interface-types/working-notes/scenarios/cc.md new file mode 100644 index 00000000..f35b41af --- /dev/null +++ b/proposals/interface-types/working-notes/scenarios/cc.md @@ -0,0 +1,251 @@ +# Pay by Credit Card + +In order to process a payment with a credit card, various items of information +are required: credit card details, amount, merchant bank, and so on. This +example illustrates the use of nominal types and record types in adapter code. + +>Note that we have substantially simplified the scenario in order to construct +>an illustrative example. + +The signature of `payWithCard` as an interface type is: + +``` +cc ::= cc{ + ccNo : u64; + name : string; + expires : { + mon : u8; + year : u16 + } + ccv : u16 +} +payWithCard:(card:cc, amount:s64, session:resource) => boolean +``` + +## Export + +In order to handle the incoming information, a record value must be passed -- +including a `string` component -- and a `session` that denotes the bank +connection must be paired with an existing connection resource. The connection +information is realized as an `eqref` (an `anyref` that supports equality). + +In this scenario, we are assuming that the credit card information is passed by +value (each field in a separate argument) to the internal implementation of the +export, and passed by reference to the import. + +``` +(@interface datatype @cc + (record "cc" + (ccNo u64) + (name string) + (expires (record + (mon u8) + (year u16) + ) + ) + (ccv u16) + ) +) + +(@interface typealias @connection eqref) + +(memory (export $memx) 1) + +(func $payWithCard_ (export ("" "payWithCard_")) + (param i64 i32 i32 i32 i32 i32 eqref) (result i32) + +(@interface func (export "payWithCard") + (param $card @cc) + (param $session (resource @connection)) + (result boolean) + + local.get $card + field.get #cc.ccNo ;; access ccNo + u64-to-i64 + + local.get $card + field.get #cc.name + string-to-memory $mem1 "malloc" + + local.get $card + field.get #cc.expires.mon + + local.get $card + field.get #cc.expires.year + + local.get $card + field.get #cc.ccv + + local.get $session + resource-to-eqref @connection + call $payWithCard_ + i32-to-enum boolean +) + +(func (export "malloc") + (param $sze i32) (result i32) + ... +) +``` + +## Import + +In this example, we assume that the credit details are passed to the import by +reference; i.e., the actual argument representing the credit card information is +a pointer to a block of memory. + +``` +(func $payWithCard_ (import ("" "payWithCard_")) + (param i32 eqref) (result i32) + +(@interface func $payWithCard (import "" "payWithCard") + (param @cc (resource @connection)) + (result boolean)) + +(@interface implement (import "" "payWithCard_") + (param $cc i32) + (param $conn eqref) + (result i32) + + local.get $cc + i64.load {offset #cc.ccNo} + i64-to-u64 + + local.get $cc + i32.load {offset #cc.name.ptr} + + local.get $cc + i32.load {offset #cc.name.len} + memory-to-string "memi" + + local.get $cc + i16.load_u {offset #cc.expires.mon} + + local.get $cc + i16.load_u {offset #cc.expires.year} + + create (record (mon u16) (year u16)) + + local.get $cc + i16.load_u {offset #cc.ccv} + + create @cc + + local.get $conn + eqref-to-resource @connection + + call $payWithCard + enum-to-i32 boolean +) +``` + +## Adapter + +Combining the import, exports and distributing the arguments, renaming `cc` for +clarity, we initially get: + +``` +(@adapter implement (import "" "payWithCard_") + (param $cc i32) + (param $conn eqref) + (result i32) + + local.get $cc + i64.load {offset #cc.ccNo} + i64-to-u64 + + local.get $cc + i32.load {offset #cc.name.ptr} + + local.get $cc + i32.load {offset #cc.name.len} + memory-to-string "memi" + + local.get $cc + i16.load_u {offset #cc.expires.mon} + + local.get $cc + i16.load_u {offset #cc.expires.year} + + create (record (mon u16) (year u16)) + + local.get $cc + i16.load_u {offset #cc.ccv} + + create @cc + + local.get $conn + eqref-to-resource @connection + + let $session (resource @connection) + let $card @cc + + local.get $card + field.get #cc.ccNo ;; access ccNo + u64-to-i64 + + local.get $card + field.get #cc.name + string-to-memory $mem1 "malloc" + + local.get $card + field.get #cc.expires.mon + u16-to-i32 + + local.get $card + field.get #cc.expires.year + u16-to-i32 + + local.get $card + field.get #cc.ccv + u16-to-i32 + + local.get $session + + resource-to-eqref @connection + call $payWithCard_ + i32-to-enum boolean + end + end + enum-to-i32 boolean +) +``` + +With some assumptions (such as no aliasing, no writing to locals, no +re-entrancy), we can propagate and inline the definitions of intermediates. This +amounts to 'regular' inlining where we recurse into records and treat the fields +of the record in an analogous fashion to arguments to the call. + +``` +(@adapter implement (import "" "payWithCard_") + (param $cc i32) + (param $conn eqref) + (result i32) + + local.get $cc + i64.load {offset #cc.ccNo} + + local.get $cc + i32.load {offset #cc.name.ptr} + + local.get $cc + i32.load {offset #cc.name.len} + string.copy Mi:"mem2" Mx:"memx" "malloc" + + local.get $cc + i16.load_u {offset #cc.expires.mon} + + local.get $cc + i16.load_u {offset #cc.expires.year} + + local.get $cc + i16.load_u {offset #cc.ccv} + + local.get $conn + call $payWithCard_ +) +``` + +There would be different sequences for the adapter if the underlying ABI were different -- +for example, if structured data were passed as a pointer to a local copy for +example. diff --git a/proposals/interface-types/working-notes/scenarios/directory.md b/proposals/interface-types/working-notes/scenarios/directory.md new file mode 100644 index 00000000..bdae6ebd --- /dev/null +++ b/proposals/interface-types/working-notes/scenarios/directory.md @@ -0,0 +1,83 @@ +# Directory Listing + +In this example, we look as an API to generate a list of files in a directory. + +The interface type signature for this function is: + +``` +listing:(string)=>sequence +``` + +Since the caller does not know how many file names will be returned, it has to +protect itself from potentially abusive situations. That in turn means that the +allocation of the string sequence in the return value may fail. + +In order to avoid memory leaks, we protect the adapter with exception handling +-- whose purpose it to clean up should an allocation failure occur + +In addition, memory allocated by the internal implementation of `$listing_` +needs to be released after the successful call. + +## Export + +``` +(memory (export "memx") 1) + +(@interface func (export "listing") + (param $dir string) + (result (sequence string)) + + local.get $dir + string-to-memory "memx" "malloc" + call $it_opendir_ + + iterator.start + sequence.start string + iterator.while $it_loop + call $it_readdir_ + dup + eqz + br_if $it_loop + memory-to-string "memx" + sequence.append + end + iterator.close + call $it_closedir + sequence.complete + end + end +) +... + +``` + +The `sequence.start`, `sequence.append` and `sequence.complete` instructions are +used to signal the creation of a sequence of values. + +In this particular example, the export adapter is not simply exporting an +individual function but is packaging a combination of three functions that, +together, implement the desired interface. This is an example of a situation +where the C/C++ language is not itself capable of realizing a concept available +in the interface type schema. + +The `$it_opendir_`, `$it_readdir_` and `$it_closedir_` functions are intended to +denote variants of the standard posix functions that have been slightly tailored +to better fit the scenario. + +The `iterator.start`, `iterator.while` and `iterator.close` instructions model +the equivalent of a `while` loop. The body of the `iterator.start` consists of +three subsections: the initialization phase, an `iterator.while` instruction +which embodies the main part of the iteration and the `iterator.close` whose +body contains instructions that must be performed at the end of the loop. + +The `iterator.while` instruction repeats its internal block until specifically +broken out of; it is effectively equivalent to the normal wasm `loop` +instruction. + +## Import + +Consuming a sequence + +``` + +``` diff --git a/proposals/interface-types/working-notes/scenarios/fetch.md b/proposals/interface-types/working-notes/scenarios/fetch.md new file mode 100644 index 00000000..a4793ead --- /dev/null +++ b/proposals/interface-types/working-notes/scenarios/fetch.md @@ -0,0 +1,222 @@ +# Fetch Url + +The `fetch` function includes a callback which is invoked whenever 'something +happens' on the session. + +It also introduces two new enumeration types: `returnCode` and `statusCode`. + +The signature of `fetch` as an interface type is: + +``` +fetch:(string,(statusCode,string)=>returnCode)=>returnCode +``` + +The callback requires an inline function definition, which is then bound to a +new closure. + +The `func.bind` adapter instruction takes two literal arguments: a type +specification of the _capture list_ (which is structured in a way that is +analogous to the param section of a function) and a literal function definition. + +The `func.bind` instruction also 'pops off' the stack as many values as are +specified in the capture signature; these are bound into a closure which is +pushed onto the stack. + +## Export + +``` +(@interface datatype @returnCode + (oneof + (enum "ok") + (enum "bad") + ) +) + +(@interface datatype @statusCode + (oneof + (enum "fail") + (enum "havedata") + (enum "eof") + ) +) + +(memory (export "memx") 1) + +(@interface func (export "fetch") + (param $u string) + (param $cb (ref (func (param $status @statusCode) (param $text string) (result @returnCode)))) + (result string) + local.get $u + string-to-memory "memx" "malloc" + + local.get $cb + func.ref $callBack + func.bind (func (param @statusCode string) (result @returnCode) + call $fetch_ + i32-to-enum @returnCode +) + +(func $callBack + (param $ecb (ref (func (param statusCode string) (result returnCode)))) + (param $status i32) + (param $text i32) + (param $len i32) + (result i32) + local.get $status + i32-to-enum @statusCode + local.get $text + local.get $len + memory-to-string "memx" + local.get $ecb + call-indirect + enum-to-i32 @returnCode +) + +(func (export "malloc") + (param $sze i32) (result i32) + ... +) +``` + +## Import + +Importing `fetch` implies exporting the function that implements the callback. + +``` +(memory (export "memi" 1) + +(func $fetch_ (import ("" "fetch_")) + (param i32 i32 (ref (func (param i32 i32)(result i32)))) (result i32)) + +(@interface func $fetch (import "" "fetch") + (param string (ref (func (param @statusCode string)) (result @returnCode))) + (result @returnCode)) + +(@interface implement (import "" "fetch_") + (param $url i32) (param $len i32) (param $callb (ref (func (param i32 i32)(result i32)))) (result i32) + local.get $url + local.get $len + memory-to-string "memi" + local.get $callb + func.ref $cbEntry + func.bind (func (param i32 i32 i32) (result i32)) + call-import $fetch + enum-to-i32 @returnCode +) + +(func $cbEntry + (param $callBk (ref (func (param i32 i32 i32) (result i32)))) + (param $status @statusCode) + (param $text string) + (result @returnCode) + local.get $status + enum-to-i32 @statusCode + local.get $text + string-to-memory "memi" "malloc" + local.get $callbk + call-indirect + i32-to-enum @returnCode +) + +(func (export "malloc") + (param $sze i32) (result i32) + ... +) +``` + +## Adapter + +Combining the export and import functions leads, in the first instance, to: + +``` +(@adapter implement (import "" "fetch_") + (param $url i32) (param $len i32) (param $callb (ref (func (param i32 i32)(result i32)))) (result i32) + local.get $url + local.get $len + memory-to-string "memi" + local.get $callb + func.ref $cbEntry + func.bind (func (param i32 i32 i32) (result i32)) + + let ($u string) ($cb (ref (func (param i32 i32 i32) (result i32)))) + local.get $u + string-to-memory "memx" "malloc" + + local.get $cb + func.ref Mx:$callBack + func.bind (func (param @statusCode string) (result @returnCode) + call Mx:$fetch_ + i32-to-enum @returnCode + enum-to-i32 @returnCode +) +``` + +Some reordering, (which is hard to sanction if we allow side-affecting +instructions), gives: + +``` +(@adapter implement (import "" "fetch_") + (param $url i32) (param $len i32) (param $callb (ref (func (param i32 i32)(result i32)))) (result i32) + local.get $url + local.get $len + string.copy Mi:"memi" Mx:"memx" Mx:"malloc" + + local.get $callb + func.ref Mi:$cbEntry + func.bind (func (param i32 i32 i32) (result i32)) + func.ref Mx:$callBack + func.bind (func (param @statusCode string) (result @returnCode) + call Mx:$fetch_ +) + +``` +The sequence of a `func.ref` followed by a `func.bind` amounts to a +partial function application and, like regular function application, can be +inlined. + +In particular, we specialize `Mx:$callBack` with the constant `Mi:$cbEntry` +replacing the bound variable `$ecb`. After inlining the now constant function +call, we get + +``` +(@adapter $callBackx func + (param $callBk (ref (func (param i32 i32 i32) (result i32)))) + (param $status i32) + (param $text i32) + (param $len i32) + (result i32) + local.get $status + + local.get $text + local.get $len + string.copy Mx:"memx" Mi:"memi" Mi:"malloc" + + local.get $callbk + call-indirect +) +``` + +and the original implementation of the adapter becomes: + +``` +(@adapter implement (import "" "fetch_") + (param $url i32) + (param $len i32) + (param $callb (ref (func (param i32 i32)(result i32)))) + (result i32) + local.get $url + local.get $len + string.copy Mi:"memi" Mx:"memx" Mx:"malloc" + + local.get $callb + func.ref Mx:$callBackx + func.bind (func (param i32 i32 i32) (result i32)) + call Mx:$fetch_ +) +``` + +Which is reasonable code. + +>Note that we are not able to specialize `Mx:$callBackx` further because the +>function value passed to `func.bind` is not a known function -- it is part of +>the `fetch` API. diff --git a/proposals/interface-types/working-notes/scenarios/getenv.md b/proposals/interface-types/working-notes/scenarios/getenv.md new file mode 100644 index 00000000..8134c096 --- /dev/null +++ b/proposals/interface-types/working-notes/scenarios/getenv.md @@ -0,0 +1,73 @@ +# Getenv Function + +The `getenv` function builds on `countCodes` by also returning a `string`. Its +interface type signature is: + +``` +getenv:(string)=>string +``` + +## Export + + +``` +(@interface func (export "getenv") + (param $str string) (result string) + local.get $str + string-to-memory "memx" "malloc" + call "getenv_" + memory-to-string "memx" +) + +(memory (export "memx") 1) + +(func (export "malloc") + (param $sze i32) (result i32) + ... +) +``` + +## Import + +The importer must also export a version of `malloc` so that the returned string can be +stored. + +``` +(func $getenv_ (import ("" "getenv_")) + (param i32 i32) (result i32 i32)) + +(@interface func $getenv (import "" "getenv") + (param $str string) (result string)) + +(@interface implement (import "" "getenv_") + (param $ptr i32) (param $len i32) (result i32)) + local.get $ptr + local.get $len + memory-to-string "memi" + call-import $getenv + string-to-memory "memi" "malloc" +) + +(memory (export "memi" 1) + +(func (export "malloc") + (param $sze i32) (result i32) + ... +) +``` + + +## Adapter + +After collapsing coercions and inlining variable bindings: + +``` +(@adapter implement (import "" "getenv_") + (param $ptr i32) (param $len i32) (result i32 i32)) + local.get $ptr + local.get $len + string.copy Mi:"memi" Mx:memx Mx:"malloc" + call Mx:"getenv_" + string.copy Mx:"memx" Mi:"memi" Mi:"malloc" +) +``` diff --git a/proposals/interface-types/working-notes/scenarios/points.md b/proposals/interface-types/working-notes/scenarios/points.md new file mode 100644 index 00000000..8d45b894 --- /dev/null +++ b/proposals/interface-types/working-notes/scenarios/points.md @@ -0,0 +1,216 @@ +# Paint a vector of points + +In this example we look at how sequences are passed into an API. The signature +of `vectorPaint` is assumed to be: + +``` +point ::= pt(i32,i32) +vectorPaint:(array pts) => returnCode +``` + +## Export + +It is assumed that the implementation of `vectorPaint_` requires a +memory-allocated array; each entry of which consists of two contiguous `i32` +values. + +The array is modeled as two values: a pointer to its base and the number of +elements in the array. + +The primary pattern with processing arrays is the for-loop pattern, together +with array indexing; which is modeled by the `for` instruction; which iterates a +code fragment over a range of numbers. + +``` +(@interface datatype @point + (oneof + (variant "pt" + (tuple i32 i32)))) + +(memory (export "memx") 1) + +(func $vectorPaint_ (export ("" "vectorPaint_")) + (param i32 i32) (result i32) + +(@interface func (export "vectorPaint") + (param $pts (array @point)) + (result @returnCode) + + local.get $pts + array-to-memory #8 "malloc" $pt $ix $ptr + local.get $pt + field.get #@point.pt.0 + s32-to-i32 + local.get $ptr + i32.store {offset 0} + local.get $pt + field.get #@point.pt.1 + s32-to-i32 + local.get $ptr + i32.store {offset 4} + end + call $vectorPaint_ + i32-to-enum @returnCode +) +``` + +The `array-to-memory` block instruction iterates over an array and executes its +block argument for each element of the array. The `array-to-memory` instruction asks the given allocator to +allocate sufficient space in linear memory for the copied out array (given by +multiplying the stride of `#8` by the number of elements in the source array. + +In addition, the `array-to-memory` instruction establishes three local variables +that are in scope for the entire operation: + +* `$pt` which is the element of the array to map + +* `$ptr` the offset within linear memory where the mapped element is located. + +* `$ix` the index of the element to map + +The `array-to-memory` instruction leaves on the stack the offset within linear +memory where the newly allocated struct is. + + +## Import + +The primary task in passing a vector of values is the construction of an `array` +to be passed to the imported function. + +``` +(func $vectorPaint_ (import ("" "vectorPaint_")) + (param i32 i32) (result i32) + +(@interface func $vectorPaint (import "" "vectorPaint") + (param @ptr (array @point)) + (result @returnCode)) + +(@interface implement (import "" "vectorPaint_") + (param $points i32) + (param $count i32) + (result i32) + + local.get $points + local.get $count + memory-to-array @point #8 $ix $pt + local.get $pt + i32.load {offset 0} + i32-to-s32 + local.get $pt + i32.load {offset 4} + i32-to-s32 + create @point + end + call $vectorPaint + enum-to-i32 @returnCode +) +``` + +The `memory-to-array` instruction is a higher-order instruction that is used to +create an array from a contiguous region of linear memory. The body of the +instruction is executed once for each element of the array in memory (the two +arguments to the instruction give the memory offset and the count); within the +body of the loop the bound variables `$ix` and `$pt` are the index of the +element and its memory offset respectively. + +The two literal operands of `memory-to-array` are the type of elements of the +constructed array and the stride length of the linear memory array. + +The body of the instruction should return an element of the resulting array; and +the instruction itself terminates with the array on the stack. + +## Adapter + +Combining the import and export sequences into an adapter code depends on being +able to fuse the generating loop with the iterating loop. + +The initial in-line version gives: + +``` +(@adapter implement (import "" "vectorPaint_") + (param $points i32) + (param $count i32) + (result i32) + + local.get $points + local.get $count + memory-to-array @point #8 $ix $pt + local.get $pt + i32.load {offset 0} + i32-to-s32 + local.get $pt + i32.load {offset 4} + i32-to-s32 + create @point + end + + let $pts + local.get $pts + array-to-memory #8 "malloc" $pt $ix $ptr + local.get $pt + field.get #@point.pt.0 + s32-to-i32 + local.get $ptr + i32.store {offset 0} + local.get $pt + field.get #@point.pt.1 + s32-to-i32 + local.get $ptr + i32.store {offset 4} + end + end + call $vectorPaint_ + i32-to-enum @returnCode + enum-to-i32 @returnCode +) +``` + +The reasoning for the next loop fusion is that the first loop is generating the +same sequence that the second loop is consuming. So, we fuse the loops by +placing the body of the second loop immediately within the first loop -- after +the construction of individual elements; and eliding the construction of the +array itself. + +``` +(@adapter implement (import "" "vectorPaint_") + (param $points i32) + (param $count i32) + (result i32) + + local.get $count ;; this one is for the eventual call to Mi:$vectorPaint_ + local.get $count + allocate #8 $arr_ "malloc" + local.get $points + local.get $count + memory.loop #8 $ix memi:$pt_ memx:$ptr_ + local.get $pt_ + i32.load {offset 0} + i32.store {offset 0} + i32.load {offset 4} + i32.store {offset 4} + end + end + call Mi:$vectorPaint_ +) +``` + +Note: The trickiest part of this is actually the handling of the counts. In +particular, the rewrite needs to be able to determine the size of the array +before copying starts. + +In some cases, by noticing that the load'n store is effectively a dense copy, +this can be further reduced to: + + +``` +(@adapter implement (import "" "vectorPaint_") + (param $points i32) + (param $count i32) + (result i32) + + local.get $points + local.get $count + array.copy #8 memi: memx: mx:malloc + call Mi:$vectorPaint_ +) +``` diff --git a/proposals/interface-types/working-notes/scenarios/scenarios.md b/proposals/interface-types/working-notes/scenarios/scenarios.md new file mode 100644 index 00000000..2e7b6476 --- /dev/null +++ b/proposals/interface-types/working-notes/scenarios/scenarios.md @@ -0,0 +1,18 @@ +# A suite of adapter scenarios + +This suite of examples is intended to demonstrate a spanning set of examples and +transformations that permit the use of interface types to specify imports and +exports. + +## Notes +Added let, func.bind, env.get, field.get, string.copy, create instructions. + +Changed arg.get to local.get because it is clearer when combined with let. + + + + + + + + diff --git a/proposals/interface-types/working-notes/scenarios/towints.md b/proposals/interface-types/working-notes/scenarios/towints.md new file mode 100644 index 00000000..b22f6e94 --- /dev/null +++ b/proposals/interface-types/working-notes/scenarios/towints.md @@ -0,0 +1,174 @@ +# Two Argument Integer Function + +Calling a two argument integer function, should result in effectively zero code. + +## Export + +``` +(@interface func (export "twizzle") + (param $a1 s32)(param $a2 s32) (result s32) + local.get $a1 + s32-to-i23 + local.get $a2 + s32-to-i32 + call "twizzle_" + i32-to-s32 +) +``` + +## Import + +``` +(@interface func (import "" "twozzle") + (param $a1 s32)(param $a2 s32) (result s32) +) +(@interface implement (import "" "twozzle_") + (param $b1 i32)(param $b2 i32) (result i32) + local.get $b1 + i32-to-s32 + local.get $b2 + i32-to-s32 + call-import "twozzle" + s32-to-i32 +) + +``` + +## Adapter Code + +The adapter code, that maps the import of `twozzle_` to its implementation as +`twizzle_` is: + +``` +(@adapter implement (import "" "twozzle_") + (param $b1 i32)(param $b2 i32) (result i32) + local.get $b1 + local.get $b2 + call Mx:"twizzle_" +) +``` + +>Note: we adopt the convention that the `Mx:` prefix refers to the exporting +>module and `Mi:` refers to the importing module. + +This should be viewed as the result of optimizations over an in-line substitution: +``` +(@adapter implement (import "" "twozzle_") + (param $b1 i32)(param $b2 i32) (result i32) + local.get $b1 + i32-to-s32 + local.get $b2 + i32-to-s32 + let s32 (local $a2 s32) + let s32 (local $a1 s32) + local.get $a1 + s32-to-i23 + local.get $a2 + s32-to-i32 + call Mx:"twizzle_" + i32-to-s32 + end + end + s32-to-i32 +) +``` + +The `let` pseudo instruction pops elements off the stack and gives them names; +and is part of the [function reference +proposal](https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md#local-bindings). + +The overall goal of the rewriting of adapters is to eliminate references to +interface types and their values. This reflects the intention to construct +executable code that implements the import of functions whose types are +specified using interface types. + +The first step in 'optimising' this sequence is to remove the `let` +instructions, where possible, and replacing instances of references to them with +the sub-sequences that gave the bound variables their value. + +For example, in + +``` +let s32 (local $a1 s32) +``` +the sub-sequence that results in the value for `$a1` is: + +``` +local.get $b1 +i32-to-s32 +``` + +so, removing the `let` for `a2`, and replacing `local.get $a2` with its defining +subsequence gives: + +``` +(@adapter implement (import "" "twozzle_") + (param $b1 i32)(param $b2 i32) (result i32) + local.get $b1 + i32-to-s32 + local.get $b2 + i32-to-s32 + let s32 (local $a2 s32) + local.get $b1 + i32-to-s32 + s32-to-i23 + local.get $a2 + s32-to-i32 + call Mx:"twizzle_" + i32-to-s32 + end + s32-to-i32 +) + +``` +and, removing the redundant pair: + +``` +i32-to-s32 +s32-to-i32 +``` +gives: + +``` +(@adapter implement (import "" "twozzle_") + (param $b1 i32)(param $b2 i32) (result i32) + local.get $b2 + i32-to-s32 + let s32 (local $a2 s32) + local.get $b1 + local.get $a2 + s32-to-i32 + call Mx:"twizzle_" + i32-to-s32 + end + s32-to-i32 +) +``` + +Repeating this for the second `let` gives: + +``` +(@adapter implement (import "" "twozzle_") + (param $b1 i32)(param $b2 i32) (result i32) + local.get $b1 + local.get $b2 + call Mx:"twizzle_" + i32-to-s32 + s32-to-i32 +) +``` + +with the final removal of the redundant coercion pair at the end: + +``` +(@adapter implement (import "" "twozzle_") + (param $b1 i32)(param $b2 i32) (result i32) + local.get $b1 + local.get $b2 + call Mx:"twizzle_" +) + +``` + +Below, we will assume that similar transformations are applied automatically; +except where we need to show what happens more clearly. diff --git a/proposals/interface-types/working-notes/scenarios/unicode_count.md b/proposals/interface-types/working-notes/scenarios/unicode_count.md new file mode 100644 index 00000000..b3bbc82c --- /dev/null +++ b/proposals/interface-types/working-notes/scenarios/unicode_count.md @@ -0,0 +1,87 @@ +# Counting Unicodes + +Passing a string and returning the number of unicode code points in it. The +interface type signature for `countCodes` is: + +``` +countCodes:(string)=>u32 +``` + +## Export + +To implement `countCodes` the incoming `string` must be mapped to the local +linear memory; which, in turn, implies invoking an allocator to find space for +it: + +``` +(@interface func (export "countCodes") + (param $str string) (result u32) + local.get $str + string-to-memory $memx "malloc" + call "countCodes_" + i32-to-u23 +) + +(memory (export "memx") 1) + +(func (export "malloc") + (param $sze i32) (result i32) + ... +) +``` + +## Import + +Importing `countCodes` involves reading a `string` out of linear memory. + +``` +(memory (export "memi" 1) +(func $count_ (import ("" "countCodes_")) + (param i32 i32) (result i32)) + +(@interface func $count (import "" "countCodes") + (param $str string) (result u32)) + +(@interface implement (import "" "countCodes_") + (param $ptr i32 $len i32) (result i32)) + local.get $ptr + local.get $len + memory-to-string "memi" + call-import "countCodes" + u32-to-i32 +) +``` + +## Adapter code + +After inlining and simple local variable binding elimination, we get a pair of +coercion operators that read a string out of one memory and write it into +another: + +``` +(@adapter implement (import "" "countCodes_") + (param $ptr i32 $len i32) (result i32)) + local.get $ptr + local.get $len + memory-to-string Mi:"memi" + string-to-memory Mx:$memx Mx:"malloc" + call Mx:"countCodes_" +) +``` +which, after collapsing coercion operators, becomes: +``` +(@adapter implement (import "" "countCodes_") + (param $ptr i32 $len i32) (result i32)) + local.get $ptr + local.get $len + string.copy Mi:"memi" Mx:"memx" Mx:"malloc" + call Mx:"countCodes_" +) +``` + +This assumes that `string.copy` combines memory allocation, string copy and +returns the new address and repeats the size of the string. + +This also assumes that the `malloc` cannot fail; below we look at exception +handling as a way of partially recovering from this failure. Without explicit +exception handling, a failed `malloc` is required to trap. From c999f8cc98b7eb075b07582deef71897c9a84346 Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Wed, 9 Oct 2019 10:56:36 -0700 Subject: [PATCH 15/23] Draft of scenario for directory listing --- .../working-notes/scenarios/directory.md | 166 +++++++++++++++++- 1 file changed, 163 insertions(+), 3 deletions(-) diff --git a/proposals/interface-types/working-notes/scenarios/directory.md b/proposals/interface-types/working-notes/scenarios/directory.md index bdae6ebd..3c7db60b 100644 --- a/proposals/interface-types/working-notes/scenarios/directory.md +++ b/proposals/interface-types/working-notes/scenarios/directory.md @@ -26,7 +26,7 @@ needs to be released after the successful call. (@interface func (export "listing") (param $dir string) (result (sequence string)) - + local.get $dir string-to-memory "memx" "malloc" call $it_opendir_ @@ -48,7 +48,6 @@ needs to be released after the successful call. end ) ... - ``` The `sequence.start`, `sequence.append` and `sequence.complete` instructions are @@ -76,8 +75,169 @@ instruction. ## Import -Consuming a sequence +Consuming a sequence involves running an iterator over the sequence, calling a +local allocator function for each found element. This is driven by the +`sequence.loop` control instruction which repeats its body once for each element +of the sequence. + +In this example, we assume that the local `$startList` and `$appendToList` +functions can be used to allocate a list structure to collect the strings from +the directory listing. In addition, the `$mkStr` function takes two `i32` +numbers and creates a local pair that can hold a string value. + +``` +(func $listing_ (import ("" "listing_")) + (param i32 i32) (result i32) + +(@interface func $listing (import "" "listing") + (param @url string) + (result (sequence string))) + +(@interface implement (import "" "listing_") + (param $text i32) + (param $count i32) + (result i32) + + local.get $text + local.get $count + memory-to-string + + call $listing + + call $startList + let $list + + sequence.loop + string-to-memory "memi" "malloc" + call $mkStr + local.get $list + call $appendToList + end + + local.get $list +) +``` + +## Adapter + +The usual first step... + +``` +(@adapter implement (import "" "listing_") + (param $text i32) + (param $count i32) + (result i32) + + local.get $text + local.get $count + memory-to-string + + let $dir + local.get $dir + string-to-memory "memx" "malloc" + call $it_opendir_ + + iterator.start + sequence.start string + iterator.while $it_loop + call $it_readdir_ + dup + eqz + br_if $it_loop + memory-to-string "memx" + sequence.append + end + iterator.close + call $it_closedir + sequence.complete + end + end + end + + call $startList + let $list + + sequence.loop + string-to-memory "memi" "malloc" + call $mkStr + local.get $list + call $appendToList + end + + local.get $list +) +``` + +After minor cleanup, and replacement of the `sequence.append` instruction with +the body of the `sequence.loop`: ``` +(@adapter implement (import "" "listing_") + (param $text i32) + (param $count i32) + (result i32) + + local.get $text + local.get $count + string.copy "memi" "memx" "malloc" + + local.get $count + call mx:$it_opendir_ + + call $startList + let $list + iterator.start + iterator.while $it_loop + call mx:$it_readdir_ + dup + eqz + br_if $it_loop + memory-to-string "memx" + string-to-memory "memi" "malloc" + call $mkStr + local.get $list + call $appendToList + end + iterator.close + call mx:$it_closedir + end + end + local.get $list +) +``` + +After cleanup and replacement of `iterator.*` instructions by their normal wasm +counterparts: + + +``` +(@adapter implement (import "" "listing_") + (param $text i32) + (param $count i32) + (result i32) + + local.get $text + local.get $count + string.copy "memi" "memx" "malloc" + + local.get $count + call mx:$it_opendir_ + + call $startList + let $list + + loop $it_loop + call mx:$it_readdir_ + dup + eqz + br_if $it_loop + string.copy "memx" "memi" "malloc" + call $mkStr + local.get $list + call $appendToList + end + call mx:$it_closedir + local.get $list +) ``` From f0ed505dab34a36768a33cabda0551f578a4d050 Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Thu, 10 Oct 2019 16:10:57 -0700 Subject: [PATCH 16/23] Start modifying explainer with record example --- proposals/interface-types/Explainer.md | 47 +++++++++++++++++++ .../interface-types/working-notes/rules.md | 31 ++++++++++++ .../working-notes/scenarios/cc.md | 34 +++++++------- 3 files changed, 95 insertions(+), 17 deletions(-) create mode 100644 proposals/interface-types/working-notes/rules.md diff --git a/proposals/interface-types/Explainer.md b/proposals/interface-types/Explainer.md index 709df1ab..18e0031e 100644 --- a/proposals/interface-types/Explainer.md +++ b/proposals/interface-types/Explainer.md @@ -165,6 +165,7 @@ other types and concepts are introduced. 1. [Shared-nothing linking example](#shared-nothing-linking-example) 1. [Strings with the GC proposal](#strings-with-the-GC-proposal) 1. [Integers](#integers) +1. [Records](#records) 1. [TODO](#TODO) ### Export returning string (statically allocated) @@ -569,6 +570,52 @@ Here we can see all the interesting possibilities: > The current set is chosen for it's practical application to C and Web IDL. + +### Records + +Records are collections of named and typed fields. Records can be defined in as +Interface Types; for example this definition encodes a familiar notion of a +credit card: + +```wat +(@interface datatype @cc + (record "cc" + (ccNo u64) + (name string) + (expires (record + (mon u8) + (year u16) + ) + ) + (ccv u16) + ) +) +``` + +Lifting and lowering a record involves lifting/lowering the individual fields of +the record and then 'packing' or 'unpacking' the record. + +For example, the sequence: + +```wat +local.get $ccNo +i64-to-u64 +local.get $name +local.get $name.len +memory-to-string +i8.local.get $mon +i8_to_u8 +i16.local.get $year +i16_to_u16 +pack @(record (mon u8) (year u16)) +local.get $ccv +pack @cc +``` +creates a credit card information record from various local variables. + +Lowering a record is complimentary, involving an `unpack` operator; together +with individual lowering operators for the fields. + ### TODO This rough list of topics is still to be added in subsequent PRs: diff --git a/proposals/interface-types/working-notes/rules.md b/proposals/interface-types/working-notes/rules.md new file mode 100644 index 00000000..01346cba --- /dev/null +++ b/proposals/interface-types/working-notes/rules.md @@ -0,0 +1,31 @@ +# Sketch of Interface Types transformation Rules + +## Lifting operators + +i32-to-s32 + +memory-to-string + +memory-to-array Instr* end + +pack @record + +## Lowering operators + +s32-to-i32 + +string-to-memory + +array-to-memory size Instr* end + +unpack @record + +## Combination Rules + +i32-to-s32 s32-to-i32 ==> empty + +memory-to-string string-to-memory ==> string.copy + +memory-to-array InstrA end array-to-memory InstrB end ==> + memory.loop InstrA InstrB end + diff --git a/proposals/interface-types/working-notes/scenarios/cc.md b/proposals/interface-types/working-notes/scenarios/cc.md index f35b41af..630a31d5 100644 --- a/proposals/interface-types/working-notes/scenarios/cc.md +++ b/proposals/interface-types/working-notes/scenarios/cc.md @@ -58,23 +58,23 @@ export, and passed by reference to the import. (param $card @cc) (param $session (resource @connection)) (result boolean) - - local.get $card - field.get #cc.ccNo ;; access ccNo - u64-to-i64 - - local.get $card - field.get #cc.name - string-to-memory $mem1 "malloc" local.get $card - field.get #cc.expires.mon - - local.get $card - field.get #cc.expires.year - - local.get $card - field.get #cc.ccv + unpack @cc $ccNo $expires $ccv + local.get $ccNo + u64-to-i64 + + local.get $expires + unpack @(record (u8) (u16)) + $mon $year + local.get $mon + u8-to-i32 + local.get $year + u16-to-i32 + end + local.get $ccv + u16-to-i32 + end local.get $session resource-to-eqref @connection @@ -124,12 +124,12 @@ a pointer to a block of memory. local.get $cc i16.load_u {offset #cc.expires.year} - create (record (mon u16) (year u16)) + pack @(record (mon u16) (year u16)) local.get $cc i16.load_u {offset #cc.ccv} - create @cc + pack @cc local.get $conn eqref-to-resource @connection From 44ca2dfa5fbd8d52a83c34258c48bbeb885a4de8 Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Fri, 11 Oct 2019 15:21:39 -0700 Subject: [PATCH 17/23] Revert "Start modifying explainer with record example" Pulled change out of Explainer for now This reverts commit f0ed505dab34a36768a33cabda0551f578a4d050. --- proposals/interface-types/Explainer.md | 47 ------------------- .../interface-types/working-notes/rules.md | 31 ------------ .../working-notes/scenarios/cc.md | 34 +++++++------- 3 files changed, 17 insertions(+), 95 deletions(-) delete mode 100644 proposals/interface-types/working-notes/rules.md diff --git a/proposals/interface-types/Explainer.md b/proposals/interface-types/Explainer.md index 18e0031e..709df1ab 100644 --- a/proposals/interface-types/Explainer.md +++ b/proposals/interface-types/Explainer.md @@ -165,7 +165,6 @@ other types and concepts are introduced. 1. [Shared-nothing linking example](#shared-nothing-linking-example) 1. [Strings with the GC proposal](#strings-with-the-GC-proposal) 1. [Integers](#integers) -1. [Records](#records) 1. [TODO](#TODO) ### Export returning string (statically allocated) @@ -570,52 +569,6 @@ Here we can see all the interesting possibilities: > The current set is chosen for it's practical application to C and Web IDL. - -### Records - -Records are collections of named and typed fields. Records can be defined in as -Interface Types; for example this definition encodes a familiar notion of a -credit card: - -```wat -(@interface datatype @cc - (record "cc" - (ccNo u64) - (name string) - (expires (record - (mon u8) - (year u16) - ) - ) - (ccv u16) - ) -) -``` - -Lifting and lowering a record involves lifting/lowering the individual fields of -the record and then 'packing' or 'unpacking' the record. - -For example, the sequence: - -```wat -local.get $ccNo -i64-to-u64 -local.get $name -local.get $name.len -memory-to-string -i8.local.get $mon -i8_to_u8 -i16.local.get $year -i16_to_u16 -pack @(record (mon u8) (year u16)) -local.get $ccv -pack @cc -``` -creates a credit card information record from various local variables. - -Lowering a record is complimentary, involving an `unpack` operator; together -with individual lowering operators for the fields. - ### TODO This rough list of topics is still to be added in subsequent PRs: diff --git a/proposals/interface-types/working-notes/rules.md b/proposals/interface-types/working-notes/rules.md deleted file mode 100644 index 01346cba..00000000 --- a/proposals/interface-types/working-notes/rules.md +++ /dev/null @@ -1,31 +0,0 @@ -# Sketch of Interface Types transformation Rules - -## Lifting operators - -i32-to-s32 - -memory-to-string - -memory-to-array Instr* end - -pack @record - -## Lowering operators - -s32-to-i32 - -string-to-memory - -array-to-memory size Instr* end - -unpack @record - -## Combination Rules - -i32-to-s32 s32-to-i32 ==> empty - -memory-to-string string-to-memory ==> string.copy - -memory-to-array InstrA end array-to-memory InstrB end ==> - memory.loop InstrA InstrB end - diff --git a/proposals/interface-types/working-notes/scenarios/cc.md b/proposals/interface-types/working-notes/scenarios/cc.md index 630a31d5..f35b41af 100644 --- a/proposals/interface-types/working-notes/scenarios/cc.md +++ b/proposals/interface-types/working-notes/scenarios/cc.md @@ -58,23 +58,23 @@ export, and passed by reference to the import. (param $card @cc) (param $session (resource @connection)) (result boolean) + + local.get $card + field.get #cc.ccNo ;; access ccNo + u64-to-i64 + + local.get $card + field.get #cc.name + string-to-memory $mem1 "malloc" local.get $card - unpack @cc $ccNo $expires $ccv - local.get $ccNo - u64-to-i64 - - local.get $expires - unpack @(record (u8) (u16)) - $mon $year - local.get $mon - u8-to-i32 - local.get $year - u16-to-i32 - end - local.get $ccv - u16-to-i32 - end + field.get #cc.expires.mon + + local.get $card + field.get #cc.expires.year + + local.get $card + field.get #cc.ccv local.get $session resource-to-eqref @connection @@ -124,12 +124,12 @@ a pointer to a block of memory. local.get $cc i16.load_u {offset #cc.expires.year} - pack @(record (mon u16) (year u16)) + create (record (mon u16) (year u16)) local.get $cc i16.load_u {offset #cc.ccv} - pack @cc + create @cc local.get $conn eqref-to-resource @connection From 99fac97bd650ebda5e8f343746d9f06d59e05a1f Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Fri, 11 Oct 2019 15:23:48 -0700 Subject: [PATCH 18/23] Draft section for explainer on records --- .../interface-types/working-notes/record.md | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 proposals/interface-types/working-notes/record.md diff --git a/proposals/interface-types/working-notes/record.md b/proposals/interface-types/working-notes/record.md new file mode 100644 index 00000000..77485dda --- /dev/null +++ b/proposals/interface-types/working-notes/record.md @@ -0,0 +1,73 @@ + + +### Records + +Records are collections of named and typed fields. Records can be defined in as +Interface Types; for example this definition encodes a familiar notion of a +credit card: + +```wat +(@interface datatype @cc + (record "cc" + (ccNo u64) + (name string) + (expires (record + (mon u8) + (year u16) + ) + ) + (ccv u16) + ) +) +``` + +Lifting and lowering a record involves lifting/lowering the individual fields of +the record and then 'packing' or 'unpacking' the record as an ordered +sub-sequence of elements on the stack. + +For example, the sequence: + +```wat +... +local.get $ccNo +i64-to-u64 +local.get $name +local.get $name.len +memory-to-string +i8.local.get $mon +i8_to_u8 +i16.local.get $year +i16_to_u16 +pack @(record (mon u8) (year u16)) +local.get $ccv +pack @cc.cc +... +``` +creates a credit card information record from various local variables. + +Lowering a record is complimentary, involving an `unpack` operator; together +with individual lowering operators for the fields: + +```wat +... +unpack @cc.cc $ccNo $name $expires $ccv + local.get $ccNo + u64-to-i64 + local.get $name + string-to-memory "malloc" + local.get $expires + unpack @(record (mon u8) (year u16)) $mon $year + local.get $mon + u8-to-i32 + local.get $year + u16-to-i32 + end + local.get $ccv + u16-to-i32 +end +``` + +The result of this unpacking is to leave the fields of the record on the +stack. If the intention were to store the record in memory then this code could +be augmented with memory store instructions. + From d3ff4d3e13100b5d06db174235aa8c9570f2138f Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Tue, 15 Oct 2019 14:45:01 -0700 Subject: [PATCH 19/23] Updated cc.md to use pack/unpack --- .../working-notes/scenarios/cc.md | 155 +++++++++++------- .../working-notes/scenarios/scenarios.md | 18 -- 2 files changed, 95 insertions(+), 78 deletions(-) delete mode 100644 proposals/interface-types/working-notes/scenarios/scenarios.md diff --git a/proposals/interface-types/working-notes/scenarios/cc.md b/proposals/interface-types/working-notes/scenarios/cc.md index f35b41af..4209f216 100644 --- a/proposals/interface-types/working-notes/scenarios/cc.md +++ b/proposals/interface-types/working-notes/scenarios/cc.md @@ -33,17 +33,22 @@ In this scenario, we are assuming that the credit card information is passed by value (each field in a separate argument) to the internal implementation of the export, and passed by reference to the import. +Lowering a record from interface types into its constituent components is +handled by the `unpack` instruction. + ``` +(@interface datatype @ccExpiry + (record + (field "mon" u8) + (field "year" u16) + ) +) (@interface datatype @cc (record "cc" - (ccNo u64) - (name string) - (expires (record - (mon u8) - (year u16) - ) - ) - (ccv u16) + (field "ccNo" u64) + (field "name" string) + (field "expires" @ccExpiry) + (field "ccv" u16) ) ) @@ -56,25 +61,29 @@ export, and passed by reference to the import. (@interface func (export "payWithCard") (param $card @cc) + (param $amount s64) (param $session (resource @connection)) (result boolean) local.get $card - field.get #cc.ccNo ;; access ccNo - u64-to-i64 + unpack @cc.cc $ccNo $name $expires $ccv + local.get $ccNo ;; access ccNo + u64-to-i64 - local.get $card - field.get #cc.name - string-to-memory $mem1 "malloc" + local.get $name + string-to-memory "mem1" "malloc" - local.get $card - field.get #cc.expires.mon - - local.get $card - field.get #cc.expires.year - - local.get $card - field.get #cc.ccv + local.get $expires + unpack @ccExpiry $mon $year + local.get $mon + local.get $year + end + + local.get @ccv + end + + local.get $amount + i64-to-s64 local.get $session resource-to-eqref @connection @@ -88,22 +97,44 @@ export, and passed by reference to the import. ) ``` +An `unpack type $x $y .. end` instruction sequence is equivalent to: + +```wat +unpack type +let $y + let $x + .. + end +end +``` + +I.e., the effect of the `unpack` instruction is to take a record off the stack +and replace it with the fields of the records, in field order. + +If local variables are specified in the instruction then this is viewed as +syntactic sugar for unpacking the record and then binding the local variables to +the fields of the record -- using the appropriate `let` instructions. + ## Import In this example, we assume that the credit details are passed to the import by reference; i.e., the actual argument representing the credit card information is a pointer to a block of memory. +Constructing a record from fields involves the use of a `pack` instruction; +which is the complement to the `unpack` instruction used above. + ``` (func $payWithCard_ (import ("" "payWithCard_")) (param i32 eqref) (result i32) (@interface func $payWithCard (import "" "payWithCard") - (param @cc (resource @connection)) + (param @cc s64 (resource @connection)) (result boolean)) (@interface implement (import "" "payWithCard_") (param $cc i32) + (param $amnt i64) (param $conn eqref) (result i32) @@ -124,17 +155,20 @@ a pointer to a block of memory. local.get $cc i16.load_u {offset #cc.expires.year} - create (record (mon u16) (year u16)) + pack @ccExpiry local.get $cc i16.load_u {offset #cc.ccv} - create @cc + pack @cc + + local.get $amnt + i64-to-s64 local.get $conn eqref-to-resource @connection - call $payWithCard + call-import $payWithCard enum-to-i32 boolean ) ``` @@ -147,6 +181,7 @@ clarity, we initially get: ``` (@adapter implement (import "" "payWithCard_") (param $cc i32) + (param $amnt i64) (param $conn eqref) (result i32) @@ -166,45 +201,42 @@ clarity, we initially get: local.get $cc i16.load_u {offset #cc.expires.year} - - create (record (mon u16) (year u16)) + + pack @ccExpiry local.get $cc i16.load_u {offset #cc.ccv} - create @cc + pack @cc + + local.get $amnt + i64-to-s64 local.get $conn eqref-to-resource @connection - + let $session (resource @connection) let $card @cc - - local.get $card - field.get #cc.ccNo ;; access ccNo - u64-to-i64 - - local.get $card - field.get #cc.name - string-to-memory $mem1 "malloc" - - local.get $card - field.get #cc.expires.mon - u16-to-i32 - - local.get $card - field.get #cc.expires.year - u16-to-i32 - - local.get $card - field.get #cc.ccv - u16-to-i32 + unpack @cc.cc $ccNo $name $expires $ccv + local.get $ccNo ;; access ccNo + u64-to-i64 + + local.get $name + string-to-memory "mem1" "malloc" + + local.get $expires + unpack @ccExpiry $mon $year + local.get $mon + local.get $year + end + + local.get @ccv + end - local.get $session - - resource-to-eqref @connection - call $payWithCard_ - i32-to-enum boolean + local.get $session + resource-to-eqref @connection + call $payWithCard_ + i32-to-enum boolean end end enum-to-i32 boolean @@ -213,12 +245,13 @@ clarity, we initially get: With some assumptions (such as no aliasing, no writing to locals, no re-entrancy), we can propagate and inline the definitions of intermediates. This -amounts to 'regular' inlining where we recurse into records and treat the fields -of the record in an analogous fashion to arguments to the call. +amounts to 'regular' inlining where we recurse into records and match up the +different packed fields with their unpacked counterparts. ``` (@adapter implement (import "" "payWithCard_") - (param $cc i32) + (param @cc i32) + (param $amnt i64) (param $conn eqref) (result i32) @@ -230,19 +263,21 @@ of the record in an analogous fashion to arguments to the call. local.get $cc i32.load {offset #cc.name.len} - string.copy Mi:"mem2" Mx:"memx" "malloc" + string.copy "memi" "memx" "malloc" local.get $cc i16.load_u {offset #cc.expires.mon} local.get $cc i16.load_u {offset #cc.expires.year} - + local.get $cc i16.load_u {offset #cc.ccv} + local.get $amnt + local.get $conn - call $payWithCard_ + call $Mx:payWithCard_ ) ``` diff --git a/proposals/interface-types/working-notes/scenarios/scenarios.md b/proposals/interface-types/working-notes/scenarios/scenarios.md deleted file mode 100644 index 2e7b6476..00000000 --- a/proposals/interface-types/working-notes/scenarios/scenarios.md +++ /dev/null @@ -1,18 +0,0 @@ -# A suite of adapter scenarios - -This suite of examples is intended to demonstrate a spanning set of examples and -transformations that permit the use of interface types to specify imports and -exports. - -## Notes -Added let, func.bind, env.get, field.get, string.copy, create instructions. - -Changed arg.get to local.get because it is clearer when combined with let. - - - - - - - - From 69c88ee108b93c3395a1b8afd0cf34120546f77d Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Tue, 15 Oct 2019 14:52:23 -0700 Subject: [PATCH 20/23] Renamed to reflect meta status --- .../working-notes/scenarios/Scenarios.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 proposals/interface-types/working-notes/scenarios/Scenarios.md diff --git a/proposals/interface-types/working-notes/scenarios/Scenarios.md b/proposals/interface-types/working-notes/scenarios/Scenarios.md new file mode 100644 index 00000000..51a8d739 --- /dev/null +++ b/proposals/interface-types/working-notes/scenarios/Scenarios.md @@ -0,0 +1,15 @@ +# A suite of adapter scenarios + +This suite of examples is intended to demonstrate a spanning set of examples and +transformations that permit the use of interface types to specify imports and +exports. + + + + + + + + + + From fe0f6b097bc9a006c2790538845721bf53b817eb Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Tue, 15 Oct 2019 14:55:14 -0700 Subject: [PATCH 21/23] Renamed, cleaned up references to different module functions --- .../working-notes/scenarios/{towints.md => twoints.md} | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) rename proposals/interface-types/working-notes/scenarios/{towints.md => twoints.md} (97%) diff --git a/proposals/interface-types/working-notes/scenarios/towints.md b/proposals/interface-types/working-notes/scenarios/twoints.md similarity index 97% rename from proposals/interface-types/working-notes/scenarios/towints.md rename to proposals/interface-types/working-notes/scenarios/twoints.md index b22f6e94..1edef126 100644 --- a/proposals/interface-types/working-notes/scenarios/towints.md +++ b/proposals/interface-types/working-notes/scenarios/twoints.md @@ -8,10 +8,10 @@ Calling a two argument integer function, should result in effectively zero code. (@interface func (export "twizzle") (param $a1 s32)(param $a2 s32) (result s32) local.get $a1 - s32-to-i23 + s32-to-i32 local.get $a2 s32-to-i32 - call "twizzle_" + call $twizzle_ i32-to-s32 ) ``` @@ -44,7 +44,7 @@ The adapter code, that maps the import of `twozzle_` to its implementation as (param $b1 i32)(param $b2 i32) (result i32) local.get $b1 local.get $b2 - call Mx:"twizzle_" + call $Mx:twizzle_ ) ``` @@ -62,7 +62,7 @@ This should be viewed as the result of optimizations over an in-line substitutio let s32 (local $a2 s32) let s32 (local $a1 s32) local.get $a1 - s32-to-i23 + s32-to-i32 local.get $a2 s32-to-i32 call Mx:"twizzle_" @@ -111,7 +111,7 @@ subsequence gives: let s32 (local $a2 s32) local.get $b1 i32-to-s32 - s32-to-i23 + s32-to-i32 local.get $a2 s32-to-i32 call Mx:"twizzle_" From 1a1f4d13f89eca65960f70ef9a9dcd1f712d1c46 Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Tue, 15 Oct 2019 15:48:36 -0700 Subject: [PATCH 22/23] Convert @ to (type $) --- .../working-notes/scenarios/cc.md | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/proposals/interface-types/working-notes/scenarios/cc.md b/proposals/interface-types/working-notes/scenarios/cc.md index 4209f216..64592a26 100644 --- a/proposals/interface-types/working-notes/scenarios/cc.md +++ b/proposals/interface-types/working-notes/scenarios/cc.md @@ -31,42 +31,42 @@ information is realized as an `eqref` (an `anyref` that supports equality). In this scenario, we are assuming that the credit card information is passed by value (each field in a separate argument) to the internal implementation of the -export, and passed by reference to the import. +export, and passed by memory reference to the import. Lowering a record from interface types into its constituent components is handled by the `unpack` instruction. ``` -(@interface datatype @ccExpiry +(@interface datatype $ccExpiry (record (field "mon" u8) (field "year" u16) ) ) -(@interface datatype @cc +(@interface datatype $cc (record "cc" (field "ccNo" u64) (field "name" string) - (field "expires" @ccExpiry) + (field "expires" (type $ccExpiry)) (field "ccv" u16) ) ) -(@interface typealias @connection eqref) +(@interface typealias $connection eqref) (memory (export $memx) 1) (func $payWithCard_ (export ("" "payWithCard_")) - (param i64 i32 i32 i32 i32 i32 eqref) (result i32) + (param i64 i32 i32 i32 i32 i32 i64 eqref) (result i32) (@interface func (export "payWithCard") - (param $card @cc) + (param $card (type $cc)) (param $amount s64) - (param $session (resource @connection)) + (param $session (resource (type $connection))) (result boolean) local.get $card - unpack @cc.cc $ccNo $name $expires $ccv + unpack (type $cc.cc) $ccNo $name $expires $ccv local.get $ccNo ;; access ccNo u64-to-i64 @@ -74,19 +74,19 @@ handled by the `unpack` instruction. string-to-memory "mem1" "malloc" local.get $expires - unpack @ccExpiry $mon $year + unpack (type $ccExpiry) $mon $year local.get $mon local.get $year end - local.get @ccv + local.get $ccv end local.get $amount i64-to-s64 local.get $session - resource-to-eqref @connection + resource-to-eqref (type $connection) call $payWithCard_ i32-to-enum boolean ) @@ -126,10 +126,10 @@ which is the complement to the `unpack` instruction used above. ``` (func $payWithCard_ (import ("" "payWithCard_")) - (param i32 eqref) (result i32) + (param i32 s64 eqref) (result i32) (@interface func $payWithCard (import "" "payWithCard") - (param @cc s64 (resource @connection)) + (param (type $cc) s64 (resource $connection)) (result boolean)) (@interface implement (import "" "payWithCard_") @@ -155,18 +155,18 @@ which is the complement to the `unpack` instruction used above. local.get $cc i16.load_u {offset #cc.expires.year} - pack @ccExpiry + pack (type $ccExpiry) local.get $cc i16.load_u {offset #cc.ccv} - pack @cc + pack (type $cc) local.get $amnt i64-to-s64 local.get $conn - eqref-to-resource @connection + eqref-to-resource (type $connection) call-import $payWithCard enum-to-i32 boolean @@ -202,22 +202,22 @@ clarity, we initially get: local.get $cc i16.load_u {offset #cc.expires.year} - pack @ccExpiry + pack (type $ccExpiry) local.get $cc i16.load_u {offset #cc.ccv} - pack @cc + pack (type $cc) local.get $amnt i64-to-s64 local.get $conn - eqref-to-resource @connection + eqref-to-resource (type $connection) - let $session (resource @connection) - let $card @cc - unpack @cc.cc $ccNo $name $expires $ccv + let $session (resource (type $connection)) + let $card (type $cc) + unpack (type $cc.cc) $ccNo $name $expires $ccv local.get $ccNo ;; access ccNo u64-to-i64 @@ -225,16 +225,16 @@ clarity, we initially get: string-to-memory "mem1" "malloc" local.get $expires - unpack @ccExpiry $mon $year + unpack (type $ccExpiry) $mon $year local.get $mon local.get $year end - local.get @ccv + local.get $ccv end local.get $session - resource-to-eqref @connection + resource-to-eqref (type $connection) call $payWithCard_ i32-to-enum boolean end @@ -250,7 +250,7 @@ different packed fields with their unpacked counterparts. ``` (@adapter implement (import "" "payWithCard_") - (param @cc i32) + (param (type $cc) i32) (param $amnt i64) (param $conn eqref) (result i32) From 71ed409317c190a2364a9ad45ad44c760a17c392 Mon Sep 17 00:00:00 2001 From: Francis McCabe Date: Mon, 21 Oct 2019 16:16:10 -0700 Subject: [PATCH 23/23] Refactored cc example slightly --- .../working-notes/scenarios/cc.md | 55 ++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/proposals/interface-types/working-notes/scenarios/cc.md b/proposals/interface-types/working-notes/scenarios/cc.md index 64592a26..2ae5dd6f 100644 --- a/proposals/interface-types/working-notes/scenarios/cc.md +++ b/proposals/interface-types/working-notes/scenarios/cc.md @@ -10,7 +10,7 @@ example illustrates the use of nominal types and record types in adapter code. The signature of `payWithCard` as an interface type is: ``` -cc ::= cc{ +cc ::= { ccNo : u64; name : string; expires : { @@ -44,7 +44,7 @@ handled by the `unpack` instruction. ) ) (@interface datatype $cc - (record "cc" + (record (field "ccNo" u64) (field "name" string) (field "expires" (type $ccExpiry)) @@ -66,7 +66,7 @@ handled by the `unpack` instruction. (result boolean) local.get $card - unpack (type $cc.cc) $ccNo $name $expires $ccv + unpack (type $cc) $ccNo $name $expires $ccv local.get $ccNo ;; access ccNo u64-to-i64 @@ -83,7 +83,7 @@ handled by the `unpack` instruction. end local.get $amount - i64-to-s64 + s64-to-i64 local.get $session resource-to-eqref (type $connection) @@ -100,7 +100,7 @@ handled by the `unpack` instruction. An `unpack type $x $y .. end` instruction sequence is equivalent to: ```wat -unpack type +unpack let $y let $x .. @@ -139,26 +139,26 @@ which is the complement to the `unpack` instruction used above. (result i32) local.get $cc - i64.load {offset #cc.ccNo} + i64.load {offset #cc_ccNo} i64-to-u64 local.get $cc - i32.load {offset #cc.name.ptr} + i32.load {offset #cc_name_ptr} local.get $cc - i32.load {offset #cc.name.len} + i32.load {offset #cc_name_len} memory-to-string "memi" local.get $cc - i16.load_u {offset #cc.expires.mon} + i16.load_u {offset #cc_expires_mon} local.get $cc - i16.load_u {offset #cc.expires.year} + i16.load_u {offset #cc_expires_year} pack (type $ccExpiry) local.get $cc - i16.load_u {offset #cc.ccv} + i16.load_u {offset #cc_ccv} pack (type $cc) @@ -173,39 +173,42 @@ which is the complement to the `unpack` instruction used above. ) ``` +The constants of the form `#cc_name_ptr` refer to offsets within the credit card +structure as laid out in memory. + ## Adapter Combining the import, exports and distributing the arguments, renaming `cc` for clarity, we initially get: ``` -(@adapter implement (import "" "payWithCard_") +(func $Mi:payWithCard (param $cc i32) (param $amnt i64) (param $conn eqref) (result i32) local.get $cc - i64.load {offset #cc.ccNo} + i64.load {offset #cc_ccNo} i64-to-u64 local.get $cc - i32.load {offset #cc.name.ptr} + i32.load {offset #cc_name_ptr} local.get $cc - i32.load {offset #cc.name.len} + i32.load {offset #cc_name_len} memory-to-string "memi" local.get $cc - i16.load_u {offset #cc.expires.mon} + i16.load_u {offset #cc_expires_mon} local.get $cc - i16.load_u {offset #cc.expires.year} + i16.load_u {offset #cc_expires_year} pack (type $ccExpiry) local.get $cc - i16.load_u {offset #cc.ccv} + i16.load_u {offset #cc_ccv} pack (type $cc) @@ -217,7 +220,7 @@ clarity, we initially get: let $session (resource (type $connection)) let $card (type $cc) - unpack (type $cc.cc) $ccNo $name $expires $ccv + unpack (type $cc) $ccNo $name $expires $ccv local.get $ccNo ;; access ccNo u64-to-i64 @@ -249,30 +252,30 @@ amounts to 'regular' inlining where we recurse into records and match up the different packed fields with their unpacked counterparts. ``` -(@adapter implement (import "" "payWithCard_") +(func $Mi:payWithCard (param (type $cc) i32) (param $amnt i64) (param $conn eqref) (result i32) local.get $cc - i64.load {offset #cc.ccNo} + i64.load {offset #cc_ccNo} local.get $cc - i32.load {offset #cc.name.ptr} + i32.load {offset #cc_name.ptr} local.get $cc - i32.load {offset #cc.name.len} + i32.load {offset #cc_name.len} string.copy "memi" "memx" "malloc" local.get $cc - i16.load_u {offset #cc.expires.mon} + i16.load_u {offset #cc_expires.mon} local.get $cc - i16.load_u {offset #cc.expires.year} + i16.load_u {offset #cc_expires.year} local.get $cc - i16.load_u {offset #cc.ccv} + i16.load_u {offset #cc_ccv} local.get $amnt