Skip to content

Commit f099487

Browse files
committed
Back out Canonical ABI mangling scheme in preparation for future alternative
1 parent aa563a1 commit f099487

File tree

3 files changed

+0
-519
lines changed

3 files changed

+0
-519
lines changed

design/mvp/CanonicalABI.md

Lines changed: 0 additions & 275 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,281 +1313,6 @@ the AOT compiler as requiring an intermediate copy to implement the above
13131313
`lift`-then-`lower` semantics.
13141314

13151315

1316-
## Canonical ABI
1317-
1318-
The above `canon` definitions are parameterized, giving each component a small
1319-
space of ABI options for interfacing with its contained core modules. Moreover,
1320-
each component can choose its ABI options independently of each other component,
1321-
with compiled adapter trampolines handling any conversions at cross-component
1322-
call boundaries. However, in some contexts, it is useful to fix a **single**,
1323-
"**canonical**" ABI that is fully determined by a given component type (which
1324-
itself is fully determined by a set of [`wit`](WIT.md) files). For example,
1325-
this allows existing Core WebAssembly toolchains to continue targeting [WASI]
1326-
by importing and exporting fixed Core Module functions signatures, without
1327-
having to add any new component-model concepts.
1328-
1329-
To support these use cases, the following section defines two new mappings:
1330-
1. `canonical-module-type : componenttype -> core:moduletype`
1331-
2. `lift-canonical-module : core:module -> component`
1332-
1333-
The `canonical-module-type` mapping defines the collection of core function
1334-
signatures that a core module must import and export to implement the given
1335-
component type via the Canonical ABI.
1336-
1337-
The `lift-canonical-module` mapping defines the runtime behavior of a core
1338-
module that has successfully implemented `canonical-module-type` by fixing
1339-
a canonical set of ABI options that are passed to the above-defined `canon`
1340-
definitions.
1341-
1342-
Together, these definitions are intended to satisfy the invariant:
1343-
```
1344-
for all m : core:module and ct : componenttype:
1345-
module-type(m) = canonical-module-type(ct) implies ct = type-of(lift-canonical-module(m))
1346-
```
1347-
One consequence of this is that the canonical `core:moduletype` must encode
1348-
enough high-level type information for `lift-canonical-module` to be able to
1349-
reconstruct a working component. This is achieved using [name mangling]. Unlike
1350-
traditional C-family name mangling, which have a limited character set imposed
1351-
by linkers and aim to be space-efficient enough to support millions of
1352-
*internal* names, the Canonical ABI can use any valid UTF-8 string and only
1353-
needs to mangle *external* names, of which there will only be a handful.
1354-
Therefore, squeezing out every byte is a lower concern and so, for simplicity
1355-
and readability, type information is mangled using a subset of the
1356-
[`wit`](WIT.md) syntax.
1357-
1358-
One final point of note is that `lift-canonical-module` is only able to produce
1359-
a *subset* of all possible components (e.g., not covering nesting and
1360-
virtualization scenarios); to express the full variety of components, a
1361-
toolchain needs to emit proper components directly. Thus, the Canonical ABI
1362-
serves as an incremental adoption path to the full component model, allowing
1363-
existing Core WebAssembly toolchains to produce simple components simply by
1364-
emitting module imports and exports with the appropriate mangled names (e.g.,
1365-
in LLVM using the [`import_name`] and [`export_name`] attributes).
1366-
1367-
1368-
### Canonical Module Type
1369-
1370-
For the same reason that core module and component [binaries](Binary.md)
1371-
include a version number (that is intended to never change after it reaches
1372-
1.0), the Canonical ABI defines its own version that is explicitly declared by
1373-
a core module. Before reaching stable 1.0, the Canonical ABI is explicitly
1374-
allowed to make breaking changes, so this version also serves the purpose of
1375-
coordinating breaking changes in pre-1.0 tools and runtimes.
1376-
```python
1377-
CABI_VERSION = '0.1'
1378-
```
1379-
Working top-down, a canonical module type is defined by the following mapping:
1380-
```python
1381-
def canonical_module_type(ct: ComponentType) -> ModuleType:
1382-
start_params, import_funcs = mangle_instances(ct.imports)
1383-
start_results, export_funcs = mangle_instances(ct.exports)
1384-
1385-
imports = []
1386-
for name,ft in import_funcs:
1387-
flat_ft = flatten_functype(ft, 'lower')
1388-
imports.append(CoreImportDecl('', mangle_funcname(name, ft), flat_ft))
1389-
1390-
exports = []
1391-
exports.append(CoreExportDecl('cabi_memory', CoreMemoryType(initial=0, maximum=None)))
1392-
exports.append(CoreExportDecl('cabi_realloc', CoreFuncType(['i32','i32','i32','i32'], ['i32'])))
1393-
1394-
start_ft = FuncType(start_params, start_results)
1395-
start_name = mangle_funcname('cabi_start{cabi=' + CABI_VERSION + '}', start_ft)
1396-
exports.append(CoreExportDecl(start_name, flatten_functype(start_ft, 'lift')))
1397-
1398-
for name,ft in export_funcs:
1399-
flat_ft = flatten_functype(ft, 'lift')
1400-
exports.append(CoreExportDecl(mangle_funcname(name, ft), flat_ft))
1401-
if any(contains_dynamic_allocation(t) for t in ft.results):
1402-
exports.append(CoreExportDecl('cabi_post_' + name, CoreFuncType(flat_ft.results, [])))
1403-
1404-
return ModuleType(imports, exports)
1405-
1406-
def contains_dynamic_allocation(t):
1407-
match despecialize(t):
1408-
case String() : return True
1409-
case List(t) : return True
1410-
case Record(fields) : return any(contains_dynamic_allocation(f.t) for f in fields)
1411-
case Variant(cases) : return any(contains_dynamic_allocation(c.t) for c in cases)
1412-
case _ : return False
1413-
```
1414-
This definition starts by mangling all nested instances into the names of the
1415-
leaf fields, so that instances can be subsequently ignored. Next, each
1416-
component-level function import/export is mapped to corresponding core function
1417-
import/export with the function type mangled into the name. Additionally, each
1418-
export whose return type implies possible dynamic allocation is given a
1419-
`post-return` function so that it can deallocate after the caller reads the
1420-
return value. Lastly, all value imports and exports are concatenated into a
1421-
synthetic `cabi_start` function that is called immediately after instantiation.
1422-
1423-
For imports (which in Core WebAssembly are [two-level]), the first-level name
1424-
is set to be a zero-length string so that the entire rest of the first-level
1425-
string space is available for [shared-everything dynamic linking].
1426-
1427-
For imports and exports, the Canonical ABI assumes that `_` is not a valid
1428-
character in a component-level import/export (as is currently the case in `wit`
1429-
[identifiers](WIT.md#identifiers)) and thus can safely be used to prefix
1430-
auxiliary Canonical ABI-induced imports/exports.
1431-
1432-
#### Instance type mangling
1433-
1434-
Instance-type mangling recursively builds a dotted path string (of instance names)
1435-
that is included in the mangled core import/export name:
1436-
```python
1437-
def mangle_instances(xs, path = ''):
1438-
values = []
1439-
funcs = []
1440-
for x in xs:
1441-
name = path + x.name
1442-
match x.t:
1443-
case ValueType(t):
1444-
values.append( (name, t) )
1445-
case FuncType(params,results):
1446-
funcs.append( (name, x.t) )
1447-
case InstanceType(exports):
1448-
vs,fs = mangle_instances(exports, name + '.')
1449-
values += vs
1450-
funcs += fs
1451-
case TypeType(bounds):
1452-
assert(False) # TODO: resource types
1453-
case ComponentType(imports, exports):
1454-
assert(False) # TODO: `canon instantiate`
1455-
case ModuleType(imports, exports):
1456-
assert(False) # TODO: canonical shared-everything linking
1457-
return (values, funcs)
1458-
```
1459-
The three `TODO` cases are intended to be filled in by future PRs extending
1460-
the Canonical ABI.
1461-
1462-
#### Function type mangling
1463-
1464-
Function types are mangled into [`wit`](WIT.md)-compatible syntax:
1465-
```python
1466-
def mangle_funcname(name, ft):
1467-
params = mangle_named_types(ft.params)
1468-
if len(ft.results) == 1 and isinstance(ft.results[0], ValType):
1469-
results = mangle_valtype(ft.results[0])
1470-
else:
1471-
results = mangle_named_types(ft.results)
1472-
return f'{name}: func{params} -> {results}'
1473-
1474-
def mangle_named_types(nts):
1475-
assert(all(type(nt) == tuple and len(nt) == 2 for nt in nts))
1476-
mangled_elems = (nt[0] + ': ' + mangle_valtype(nt[1]) for nt in nts)
1477-
return '(' + ', '.join(mangled_elems) + ')'
1478-
```
1479-
1480-
#### Value type mangling
1481-
1482-
Value types are similarly mangled into [`wit`](WIT.md)-compatible syntax,
1483-
recursively:
1484-
1485-
```
1486-
def mangle_valtype(t):
1487-
match t:
1488-
case Bool() : return 'bool'
1489-
case S8() : return 's8'
1490-
case U8() : return 'u8'
1491-
case S16() : return 's16'
1492-
case U16() : return 'u16'
1493-
case S32() : return 's32'
1494-
case U32() : return 'u32'
1495-
case S64() : return 's64'
1496-
case U64() : return 'u64'
1497-
case Float32() : return 'float32'
1498-
case Float64() : return 'float64'
1499-
case Char() : return 'char'
1500-
case String() : return 'string'
1501-
case List(t) : return 'list<' + mangle_valtype(t) + '>'
1502-
case Record(fields) : return mangle_recordtype(fields)
1503-
case Tuple(ts) : return mangle_tupletype(ts)
1504-
case Flags(labels) : return mangle_flags(labels)
1505-
case Variant(cases) : return mangle_varianttype(cases)
1506-
case Enum(labels) : return mangle_enumtype(labels)
1507-
case Union(ts) : return mangle_uniontype(ts)
1508-
case Option(t) : return mangle_optiontype(t)
1509-
case Result(ok,error) : return mangle_resulttype(ok,error)
1510-
1511-
def mangle_recordtype(fields):
1512-
mangled_fields = (f.label + ': ' + mangle_valtype(f.t) for f in fields)
1513-
return 'record { ' + ', '.join(mangled_fields) + ' }'
1514-
1515-
def mangle_tupletype(ts):
1516-
return 'tuple<' + ', '.join(mangle_valtype(t) for t in ts) + '>'
1517-
1518-
def mangle_flags(labels):
1519-
return 'flags { ' + ', '.join(labels) + ' }'
1520-
1521-
def mangle_varianttype(cases):
1522-
mangled_cases = ('{label}{payload}'.format(
1523-
label = c.label,
1524-
payload = '' if c.t is None else '(' + mangle_valtype(c.t) + ')')
1525-
for c in cases)
1526-
return 'variant { ' + ', '.join(mangled_cases) + ' }'
1527-
1528-
def mangle_enumtype(labels):
1529-
return 'enum { ' + ', '.join(labels) + ' }'
1530-
1531-
def mangle_uniontype(ts):
1532-
return 'union { ' + ', '.join(mangle_valtype(t) for t in ts) + ' }'
1533-
1534-
def mangle_optiontype(t):
1535-
return 'option<' + mangle_valtype(t) + '>'
1536-
1537-
def mangle_resulttype(ok, error):
1538-
match (ok, error):
1539-
case (None, None) : return 'result'
1540-
case (None, _) : return 'result<_, ' + mangle_valtype(error) + '>'
1541-
case (_, None) : return 'result<' + mangle_valtype(ok) + '>'
1542-
case (_, _) : return 'result<' + mangle_valtype(ok) + ', ' + mangle_valtype(error) + '>'
1543-
```
1544-
As an example, given a component type:
1545-
```wasm
1546-
(component
1547-
(import "foo" (func))
1548-
(import "a" (instance
1549-
(export "bar" (func (param "x" u32) (param "y" u32) (result u32)))
1550-
))
1551-
(import "v1" (value string))
1552-
(export "baz" (func (param "s" string) (result string)))
1553-
(export "v2" (value list<list<string>>))
1554-
)
1555-
```
1556-
the `canonical_module_type` would be:
1557-
```wasm
1558-
(module
1559-
(import "" "foo: func() -> ()" (func))
1560-
(import "" "a.bar: func(x: u32, y: u32) -> u32" (func param i32 i32) (result i32))
1561-
(export "cabi_memory" (memory 0))
1562-
(export "cabi_realloc" (func (param i32 i32 i32 i32) (result i32)))
1563-
(export "cabi_start{cabi=0.1}: func(v1: string) -> (v2: list<list<string>>)" (func (param i32 i32) (result i32)))
1564-
(export "baz: func(s: string) -> string" (func (param i32 i32) (result i32)))
1565-
(export "cabi_post_baz" (func (param i32)))
1566-
)
1567-
```
1568-
1569-
### Lifting Canonical Modules
1570-
1571-
TODO
1572-
1573-
```python
1574-
class Module:
1575-
t: ModuleType
1576-
instantiate: Callable[typing.List[typing.Tuple[str,str,Value]], typing.List[typing.Tuple[str,Value]]]
1577-
1578-
class Component:
1579-
t: ComponentType
1580-
instantiate: Callable[typing.List[typing.Tuple[str,any]], typing.List[typing.Tuple[str,any]]]
1581-
1582-
def lift_canonical_module(module: Module) -> Component:
1583-
# TODO: define component.instantiate by:
1584-
# 1. creating canonical import adapters
1585-
# 2. creating a core module instance that imports (1)
1586-
# 3. creating canonical export adapters from the exports of (2)
1587-
pass
1588-
```
1589-
1590-
15911316

15921317
[Canonical Definitions]: Explainer.md#canonical-definitions
15931318
[`canonopt`]: Explainer.md#canonical-definitions

0 commit comments

Comments
 (0)