Skip to main content

WAI Extension: WA²ISM™, the Deterministic Module Profile

Mirrored from the canonical text at commit 260cbd09 ().

Status: Draft. WA²ISM™, named WAISM™ in running text, is WAI’s deterministic module profile: a checked subset of standard WebAssembly, run by unmodified engines. It is not a fork of WebAssembly and not a new bytecode: it adds no instruction, type, section or semantics, and any engine that implements WebAssembly 3.0 runs a module that meets it. What the profile decides is what a sink may state about such a module’s output. It has two levels (§2): an integer level, with no floating point, whose output is the same on every engine with no engine setting; and a float level, which admits floating point and holds a tier only on an engine set to the NaN rule of the deterministic profile of WebAssembly 3.0. A sink checks a decoder module against numbered rules (§4) before it instantiates it, holds a signed conformance result over the module’s digest (§6, check 9c), runs it through a fixed interface (§5) under the settings of §7, refuses with the codes of §8, and names what it checked in its receipt (§10). Checks 9 to 13 follow jwp-receipts §7.9’s checks 1 to 8, which authorise the module and which this text leaves as they are. The corpora a checker, a module and an engine are held to are in §9. This text defines the profile; it names no implementation. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.

Texts read for this revision: WebAssembly 3.0, the specification build dated 2026-11-04: the appendix Profiles (Defined Profiles: the full profile, FUL, and the deterministic profile, DET); the appendix Implementation Limitations (Execution); Execution, Numerics (Floating-Point Operations: Rounding, NaN Propagation; Relaxed Operations) and Instructions (memory.grow); Binary Format, Types (limits) and Modules (the preamble, the section ids, custom sections, the start section). A section number in this text is this extension’s unless it names another document.

1. Scope

In scope: the two levels (§2); the rules a module meets, the caps and a checker’s verdict (§4); the interface a module exports (§5); the steps a sink takes to check and run a module (§6); the engine settings it uses (§7); the refusal codes (§8); the corpora (§9); what a receipt states (§10); and conformance (§11).

Out of scope: how a sink obtains a module and who may grant it (jwp-receipts §7.9, http-delivery §8.4); reconstruct modules, and decoder modules a sink runs on no WebAssembly engine, which this text does not cover; the arithmetic a decoder computes, which its capability’s text fixes (integer-operators for the integer graphs); and a pinned parameter set, which is data and is held by jwp-receipts §7.9’s checks 1 to 8 alone.

Relationship to WebAssembly and to the other texts:

2. Levels

The profile’s identifiers are defined here, and only here. Every other text, this one included, names a level as the integer level or the float level.

levelidentifierfloating pointenginewhat its output holds
integer levelwaism/1-intrefused (R3)any engine that implements WebAssembly 3.0, a browser’s included, with no settingthe class tier of its capability (SPEC §3.1, Tier) and, for a GenerativeDeterministic capability, its generation-equivalence (SPEC §7)
float levelwaism/1-detadmittedonly an engine set to the deterministic profile’s NaN rule, shown by the probe (§6, check 10); no page (§7)entropy-consistency where that is the class tier of its capability, on such an engine; otherwise none. Never decode-equivalence or generation-equivalence: SPEC §7, Replicate-soundness, admits a neural decode at decode-equivalence only where no floating-point operation is on its path, and this revision gives the float level neither

The levels differ only in R3. Each identifier names this text’s version 1; a module meets a level when a checker gives it held at that level (§4.3), and R13.

3. Terms

4. Rules

4.1 The rules

Each rule names what it rests on: a clause of WebAssembly 3.0, or a clause of WAI. §9.1 names, for each rule, at least one planted module that breaks it and no other.

rulelevelthe modulerests on
R1bothis at most module_bytes long (§4.2).WebAssembly 3.0, Implementation Limitations (an implementation may limit the size of a module); §4.2
R2bothis a WebAssembly 3.0 module: its bytes decode under the binary format, from the preamble \0asm and version 1, and it is valid, within the validation limits of §4.2. Shared memory, atomic instructions, a component, and every other construct WebAssembly 3.0 does not define, are outside it.WebAssembly 3.0, Binary Format (Modules: the preamble and the section ids; Types: limits, whose four forms have no shared one), Validation and Implementation Limitations (an implementation may limit the number and size of a module’s parts); §4.2
R3integercontains no floating point: no f32 or f64 value type, and no instruction that takes or gives a floating-point value, an f32 or an f64 or a v128 read or written as f32x4 or f64x2 lanes. That covers constants, arithmetic, comparisons, conversions to and from integers (the saturating truncations among them), reinterpretations, lane splats and extractions, and loads and stores of floating-point values.WebAssembly 3.0, Numerics, NaN Propagation (the sign of a NaN result is non-deterministic, and its payload is chosen among arithmetic NaNs); SPEC §7, Replicate-soundness (no float operation anywhere on a decode path that backs replicate); integer-operators §6
R4bothcontains no relaxed vector instruction: the integer ones (i8x16.relaxed_swizzle, the four relaxed_laneselects, i16x8.relaxed_q15mulr_s, the two relaxed dot products) as well as the floating-point ones.WebAssembly 3.0, Numerics, Relaxed Operations (each result is one of a set, and the implementation chooses which); integer-operators §6 (no instruction whose result the instruction set leaves to the implementation)
R5bothimports nothing: no function, table, memory, global or tag.WebAssembly 3.0, Binary Format, Modules (the import section, whose values the host supplies at instantiation); §5 (the output is a function of the capability, the payload and the set alone)
R6bothdefines exactly one memory, with address type i32 and a declared maximum of at most memory_pages (§4.2).WebAssembly 3.0, Execution, memory.grow (growth is non-deterministic, and fails past a declared maximum); Binary Format, Types (limits); §4.2
R7bothdefines at most one table, whose element type is funcref (ref null func, in either of its encodings), whose address type is i32 and whose minimum equals its maximum, and contains no table.grow, table.fill or table.set.WebAssembly 3.0, Profiles, Defined Profiles (table.grow remains non-deterministic under the deterministic profile); §5
R8bothhas no start section.WebAssembly 3.0, Binary Format, Modules (the start section names a function that runs at instantiation); §6 check 12 (the guard covers wai_run, not instantiation)
R9bothgives no function type, local or global a reference type.§5 (every value across the interface is an i32)
R10bothexports exactly the six exports of §5, each of the kind and type given there, and nothing else.§5
R11bothdefines at most functions functions (§4.2).WebAssembly 3.0, Implementation Limitations; §4.2
R12bothcontains none of these constructs of WebAssembly 3.0: exception handling (a tag section, throw, throw_ref, try_table); tail calls (return_call, return_call_indirect); typed function references (call_ref, return_call_ref, ref.as_non_null, br_on_null, br_on_non_null); and garbage-collected types and instructions (a type entry that is not a function type, among them a recursive or subtype declaration; struct.*, array.*, ref.i31, i31.get_s, i31.get_u, ref.test, ref.cast, br_on_cast, br_on_cast_fail, any.convert_extern, extern.convert_any, ref.eq).§1 (the profile admits what a decoder module uses: a construct a decoder has no need of is one more that a sink’s engine would run and that a conformance result would have to answer for, §11)
R13bothafter a memory.grow that fails, gives no output: within wai_run the call traps, and within wai_alloc it traps or returns 0. A module whose memory’s minimum equals its maximum and which contains no memory.grow meets this rule.WebAssembly 3.0, Execution, memory.grow (it may fail, pushing −1) and Profiles (growth remains non-deterministic under the deterministic profile); integer-operators §6 (“A refused decode outputs nothing”)

An instruction breaks R3 by what its opcode alone takes or gives. One whose operand or result types are fixed elsewhere (a block, a typed select, a call, local.get, drop) breaks R3 only through the value type that fixes them: an earlier item, or, for a block type or a typed select, the value type that follows its opcode.

Rules R1 to R12 are about the module’s bytes, and a checker decides them (§4.3). R13 is about what the module does, and a conformance result attests it (§6, check 9c; §9.2). Custom sections are part of the bytes, so the digest covers them; WebAssembly’s semantics ignore them, and no rule, check or sink of this text reads them.

4.2 Caps and validation limits

capthe profile’s valuewhat it bounds
module_bytes16,777,216the module’s length, in bytes
functions50,000the functions the module defines
memory_pages65,536the memory’s declared maximum, in 64 KiB pages

A sink MAY apply lower caps of its own. A module over a sink’s caps is not held at that sink: the caps change where a module is held, never what it outputs.

The validation limits are part of R2, and no sink changes them. WebAssembly 3.0 lets an implementation limit the number and the size of a module’s parts, so without fixed limits two validators could differ on a module within the caps; a module beyond one of these is not valid under this profile, and is refused R2 -.

limitvaluewhat it bounds
types1,000,000the entries of the type section, and the types they define
functions, globals, tags1,000,000 eachthose the module imports and defines, together
tables100,000the tables the module imports and defines, together
memories100the memories the module imports and defines, together
element segments, data segments100,000 eachthe entries of the element section, and of the data section; and the data count section’s count
segment items10,000,000the items of one element segment
name100,000the bytes of one name: an import’s module or field, an export’s, a custom section’s
body7,654,321the bytes of one function body, after its size
locals50,000a function’s parameters and the locals its body declares, together
parameters, results1,000 eachthose of one function type
struct fields10,000those of one struct type
catches10,000the catch clauses of one try_table
subtype depth63a type’s declared supertypes, followed to a type that declares none
interface999,998the sum, over every import and export, of 2 and the parameters and results of its type for a function or a tag, and of 1 for a table, a memory or a global; so a module has at most 999,998 imports and exports

4.3 The verdict

A checker’s verdict for a module at a level under caps is one line of ASCII: held, or refused R<n> <offset>, with <n> the rule and <offset> a decimal byte offset into the module, or -. It is a function of the module’s bytes, the level and the caps, and it is decided in this order:

  1. R1: a module longer than module_bytes is refused R1 0, and is not read further.
  2. R2: a module that does not decode, or is not valid, is refused R2 -. Decoders and validators report such a failure at different offsets, so this verdict carries none.
  3. R3 to R12, over the items of the module in binary order: the first item that breaks one of them is refused at its offset, by the lowest-numbered rule it breaks. The offset of an instruction is that of its first byte (for a prefixed opcode, the prefix); of a value type, its first byte; of an entry or a type entry, its first byte; of a section, its id byte. R11 is broken by the function section; a table instruction that R7 refuses, by that instruction.
  4. Then the rules an absence breaks, in rule order, at the offset equal to the module’s length: R6 where the module defines no memory, and R10 where an export of §5 is missing.
  5. Otherwise held.

A module that breaks rules at several items is refused at the first; a sink reports that rule. Since the levels differ only in R3, a module refused R3 at the integer level is checked at the float level, and every other verdict is the same at both.

5. The module ABI

A module exports exactly these:

exportkind and typewhat it does
memorythe module’s one memoryholds every buffer the sink passes in and reads out
wai_abifunction () → i32returns 1, the version of this interface
wai_allocfunction (len: i32) → i32returns the offset of a region of len bytes the module will not otherwise use, or 0 where it cannot
wai_runfunction (cap_ptr: i32, cap_len: i32, in_ptr: i32, in_len: i32, set_ptr: i32, set_len: i32) → i32decodes one object; returns 0 when the output is the decoded medium, or 1 when it is a refusal code
wai_out_ptrfunction () → i32the offset of the output
wai_out_lenfunction () → i32the output’s length, in bytes

A sink decodes an object through a module on an instance of its own: it instantiates the module for that object, makes these calls once each, in this order, and discards the instance.

  1. wai_abi(). A result other than 1 is module_result_invalid.
  2. For each of three inputs, in this order: the capability, as the UTF-8 bytes of its term; the payload, the bytes the envelope carries for that capability; and the set, the pinned parameter set in its pin’s form, which SPEC §3.1’s checks 6–8 have held to its digest. The sink calls wai_alloc(len) with the input’s length and writes the input at the offset returned. A result of 0 is module_resources. A capability that decodes against no set passes 0, 0 for it, with no call.
  3. wai_run with the three offsets and lengths, under the guard of §6 check 12.
  4. wai_out_ptr() and wai_out_len(). A region that does not lie within the memory is module_result_invalid.
  5. Where wai_run returned 0, the region is the decoded medium in its capability’s canonical buffer (determinism-tiers §2: canonical RGB8 for an image, int16 PCM, the attribute tensor, a per-frame hash for video). Where it returned 1, the region is a refusal code in ASCII, one of the codes the text that defines the capability’s payload lists for its decode, and the decode is refused with that code, as that text routes it. Any other result of wai_run, or a refusal text that is not such a code, is module_result_invalid.

Nothing else reaches the module: it imports nothing (R5), runs nothing at instantiation (R8), and starts from its own bytes on every instance. So the output is a function of the capability, the payload and the set. A decode that reads anything else, such as the decoded state of another object, is not one this interface carries.

6. How a sink checks and runs a module

A sink that decodes an object through a module takes these steps in order and reports the first that fails. A failure at steps 1 to 10 means the module is not held for the object: the capability is unsupported for that envelope at that sink, and dispatch continues to the declared fallback or a sibling rendition (jwp-receipts §7.9, Fail closed, Fallback, never inert). A failure at steps 11 to 13 refuses the object’s decode through the module, and dispatch continues the same way; nothing is output.

0. Identity. The SHA-256 of the exact bytes the sink will instantiate (jwp-receipts §7.9, Identity).

1–8. Authorisation. jwp-receipts §7.9’s checks 1 to 8, unchanged.

9. Profile.

10. Engine.

11. Instantiate with an empty import object. A sink SHOULD reserve the memory’s declared maximum where its engine allows it. A failure to instantiate or to reserve is module_resources. Then the calls of §5, steps 1 and 2.

12. Run wai_run under a guard: an instruction budget the engine counts, or a wall-clock limit the sink enforces (§7). A trap, a stack exhausted among them, is module_trap; an exhausted budget or limit is module_timeout. A sink whose engine reports that a memory.grow failed refuses the run (module_resources) whatever the module then returns. Every sink also relies on R13, which check 9c attests: a page cannot observe a failed grow. Availability can differ between sinks; the output never does.

13. Output. §5’s steps 4 and 5. The sink hashes the decoded medium (SHA-256), and its receipt names the module as §10 states.

A module held at the integer level and run through steps 11 to 13 holds, for its output, what §2 gives that level; one held at the float level holds what §2 gives the float level, only after check 10 passed with the object’s need above none.

7. Engine settings

termthe setting
threads-offthe engine refuses shared memory and atomic instructions
relaxed-offthe engine refuses relaxed vector instructions
nan-canonicalthe engine gives every NaN a floating-point instruction generates as the positive canonical NaN, the deterministic profile’s rule
fuelthe run is bounded by a budget of instructions the engine counts
wall-clockthe run is bounded by a wall-clock limit the sink enforces

A sink, at either level:

At the float level a sink also sets nan-canonical, and check 10’s probe shows it. A sink MUST NOT take an engine setting that fixes the behaviour of relaxed vector instructions for the deterministic profile: both levels refuse those instructions (R4), and only the probe shows an engine’s NaN behaviour. A page takes check 10 as failed: the output of a float-level module in a page holds no tier, and a page decodes through one only an object whose need is none.

The engine a receipt names (§10) is written <engine>@<version>, optionally followed by /<compiler>: <engine> and <compiler> are 1 to 64 of a–z, 0–9, _ and -, and <version> is 1 to 64 of ASCII letters, digits, ., + and -, beginning with a digit. A budget’s instruction count is not energy, and no receipt states it.

8. Refusal codes

coderaised atthen
module_profile_violation§6 check 9a, 9b: with the rule and the offset of the verdict (§4.3)the module is not held for the object
module_unattested§6 check 9cthe module is not held for the object
module_engine_not_deterministic§6 check 10the module is not held for an object whose need is above none
module_probe_mismatch§6 check 10the module is not held for the object
module_resources§6 check 11 (instantiation, reservation, wai_alloc returning 0) and check 12 (a failed memory.grow the engine reports)the object’s decode through the module is refused
module_trap§6 check 12the object’s decode through the module is refused
module_timeout§6 check 12the object’s decode through the module is refused
module_result_invalid§5 steps 1, 4 and 5 (§6 checks 11 and 13)the object’s decode through the module is refused

In each case nothing is output, and dispatch continues as §6 states. A refusal the module returns through wai_run is its capability’s own, and routes as that capability’s text routes it.

9. Corpora

9.1 The checker corpus

A conforming checker gives each module below the verdict stated, at the level and under the caps stated. Each module is given by its construction: any bytes built as its row describes are that module, and the offset in its verdict is that of the item the row names, in those bytes (§4.3). Caps not stated are the profile’s (§4.2).

Planted modules.

caselevel, capsthe moduleverdict
oversizeboth; module_bytes one below the module’s lengthminimal-int, unchangedrefused R1 0
invalidbotha function whose body leaves an i64 where its type returns i32refused R2 -
shared-memorybotha shared memory and an i32.atomic.loadrefused R2 -
componentbotha component’s preamblerefused R2 -
scalar-floatintegerf32.add of two f32.consts in wai_runrefused R3, at the first f32.const
float-unreachableintegerf32.add and drop in wai_run after a return, where the operand stack is polymorphic and no earlier item gives a floating-point valuerefused R3, at the f32.add
float-block-typeintegera block in wai_run whose type is the result f32, holding only unreachable, its result droppedrefused R3, at the block type’s value type
float-select-typeintegera typed select whose type is f32 in wai_run after a return, where the operand stack is polymorphic, its result droppedrefused R3, at its value type
float-constantintegera global initialised by f64.constrefused R3, at the global’s value type
float-signatureintegera function type with an f64 parameterrefused R3, at that value type
float-vectorintegerf32x4.addrefused R3, at the instruction
float-conversionintegerf32x4.convert_i32x4_srefused R3, at the instruction
float-truncationintegeri32.trunc_sat_f32_s on an f32 built by f32.reinterpret_i32refused R3, at f32.reinterpret_i32
float-loadintegerf32.loadrefused R3, at the instruction
relaxed-swizzlebothi8x16.relaxed_swizzlerefused R4, at the instruction
relaxed-laneselectbothi16x8.relaxed_laneselectrefused R4, at the instruction
relaxed-dotbothi16x8.relaxed_dot_i8x16_i7x16_srefused R4, at the instruction
relaxed-maddfloatf32x4.relaxed_maddrefused R4, at the instruction
import-functionbothan imported function returning an i32 (a clock)refused R5, at the import
import-memorybothits memory imported, not definedrefused R5, at the import; it breaks R6 too, which §4.3 reports only after every item
import-globalbothan imported immutable i32 globalrefused R5, at the import
no-memorybothno memory: its memory, its export memory and the store in wai_run removedrefused R6, at the module’s length; it breaks R10 too, which §4.3 reports after R6
two-memoriesbotha second memoryrefused R6, at the second memory’s entry
memory64bothits memory of address type i64, and the store in wai_run addressed by an i64refused R6, at the memory’s entry
no-maximumbothits memory with no maximumrefused R6, at the memory’s entry
over-pagesboth; memory_pages 16its memory’s maximum 17refused R6, at the memory’s entry
two-tablesbothtwo funcref tables, each with its minimum its maximumrefused R7, at the second table’s entry
externref-tablebotha table of externref, its minimum its maximumrefused R7, at the table’s entry
table-unfixedbotha funcref table whose minimum is below its maximumrefused R7, at the table’s entry
table-growbotha funcref table, its minimum its maximum, and table.growrefused R7, at the instruction
table-setboththe same table, and table.setrefused R7, at the instruction
table-fillboththe same table, and table.fillrefused R7, at the instruction
startbotha start sectionrefused R8, at the section’s id byte
funcref-parameterbotha function of the module, not exported, with a funcref parameterrefused R9, at that value type
externref-globalbotha global of type externrefrefused R9, at its value type
funcref-localbotha local of type funcrefrefused R9, at its value type
i64-exportbothwai_run with its last parameter an i64refused R10, at the export’s entry
extra-exportbotha seventh export, a functionrefused R10, at that export’s entry
exported-globalbothan exported globalrefused R10, at that export’s entry
missing-runbothno export wai_runrefused R10, at the module’s length
over-functionsboth; functions 8nine functionsrefused R11, at the function section’s id byte
tail-callbothreturn_callrefused R12, at the instruction
exceptionbotha tag section and throwrefused R12, at the tag section’s id byte
struct-typebotha struct type in the type sectionrefused R12, at that type entry
call-refbothcall_ref of a ref.func naming wai_abi, which a declarative element segment declaresrefused R12, at the instruction

Every planted module but component is the module minimal-int (below) with the change named, which breaks the rule named. Two of them, import-memory and no-memory, cannot break it alone, and their rows say which other rule they break and why the verdict is the one stated. Where a row adds an instruction, unless it says otherwise, the instruction is added to wai_run before its store, its operands are i32.consts, v128.consts or ref.null func, as its type takes, and a result it leaves is dropped. None of those is a floating-point item, so the instruction is the first item of the module that breaks a rule. An instruction that takes a floating-point operand has an earlier item that gives one, which §4.3 refuses first, except in code the operand stack reaches as polymorphic, as float-unreachable places it.

Conforming modules.

casethe moduleverdict
minimal-intthe interface of §5 and nothing else: one memory of minimum 1 page and maximum 2; no table, global, data segment, element segment or custom section; wai_abi returns 1, wai_alloc returns 0, wai_run stores one fixed byte at offset 0 with i32.store8 and returns 0, wai_out_ptr returns 0 and wai_out_len returns 1; no function reads a parameterheld at both levels
vector-intminimal-int with i32x4.dot_i16x8_s, i8x16.swizzle, i8x16.shuffle and i16x8.q15mulr_sat_sheld at both levels
bulk-and-signminimal-int with memory.copy, memory.fill, i32.extend8_s and a block of two resultsheld at both levels
fixed-tableminimal-int with one funcref table, its minimum its maximum, an element segment and call_indirectheld at both levels
fixed-memoryminimal-int whose memory’s minimum is its maximum, with no memory.growheld at both levels
custom-sectionminimal-int with a custom section named profile whose contents are the float level’s identifierheld at both levels
minimal-floatminimal-int with f64.add of two f64.consts in wai_runheld at the float level; refused R3 at the integer level, at the first f64.const

R13 is not a checker’s rule; §9.2’s planted module breaks it.

9.2 The module corpus

The module corpus of a capability holds the cases a module that decodes it is attested on (§6, check 9c):

The planted module grow-swallowed meets R1 to R12 at the integer level, and, when a memory.grow within wai_run fails, continues and returns 0 with an output. Its record fails the grow-refusal case (v, 1) of any vector whose decode grows, so check 9c refuses it.

9.3 The probe

The probe is a module that meets R1 to R12 at the float level, and whose wai_run, for any input, outputs the results of the cases below, in order: each result’s bits in little-endian order, four bytes for an f32, eight for an f64, and sixteen for a v128, lane 0 first. Each input is written as its bits. A case’s expected result is the one the deterministic profile prescribes: the positive canonical NaN, 0x7fc00000 for f32 and 0x7ff8000000000000 for f64, in every lane.

caseoperationoperands
1f32.add0xffa00001, 0x3f800000
2f32.mul0xffa00001, 0x40000000
3f32.div0x00000000, 0x00000000
4f32.sub0x7f800000, 0x7f800000
5f32.sqrt0xbf800000
6f32.min0xffa00001, 0x3f800000
7f32.nearest0xffa00001
8f32.demote_f640xfff4000000000001
9f64.add0xfff4000000000001, 0x3ff0000000000000
10f64.promote_f320xffa00001
11f32x4.addlanes 0xffa00001, 0x7fa00001, 0xffc00000, 0x7f800000; lanes 0x3f800000, 0x3f800000, 0x3f800000, 0xff800000
12f64x2.mullanes 0xfff4000000000001, 0x7ff8000000000001; lanes 0x4000000000000000, 0x4000000000000000

The probe is about NaNs only. The deterministic profile’s other rule, on relaxed vector instructions, is unreachable from a module of either level (R4).

10. Receipts

A sink that records a reconstruct-session claim (jwp-receipts §2.4.12) of a decode through a module writes, in that decoder entry, the module members of that section:

A claim verifier that implements this profile checks them under jwp-receipts §2.4.12’s rules 15 to 17. One that does not refuses an entry that carries them (unknown_member), as that section states.

11. Conformance

12. What holding a module shows

That a sink holds a module at the integer level shows that the module’s output is the same on every engine that implements WebAssembly 3.0, or that there is none: a run that meets a limit is refused, never output differently. At the float level it shows the same on every engine that executes the deterministic profile’s NaN rule. The record of check 9c shows that the module, on its capability’s corpus and its grow-refusal cases, gave what the corpus states. That the module decodes every other input as its capability’s text fixes is not something the profile shows: the tier a sink states rests on the capability’s registration, the grant and the record together.