Skip to content

Commit fb8c761

Browse files
authored
Configure async task context on realloc calls (#13949)
* Configure async task context on `realloc` calls This commit is an implementation of WebAssembly/component-model#680 which is a semantic change for invocations of the `realloc` canonical ABI option. Previously when this function was invoked the configured context-storage was whatever happened to run last in the store and in general wasn't well-defined. Additionally the context of the thread being run in was whatever happened to run last. The goal of the upstream spec change, and this commit, is to fully define what happens in this situation. Specifically: * Invocations of `realloc` always start with `context` slots set to 0. * The `thread.index` intrinsics, part of the component-model-threading proposal, is now no longer exempt from may-leave checks. These changes combined mean that it's not actually possible for `realloc` to witness anything about its thread identity. This means that we don't actually have to allocate anything, all that's necessary is to save/restore context around invocation of `cabi_realloc`. While this is a breaking change it's not expected to be observable in the ecosystem. This only affects `context` slots which are primarily only used for the WASIp3 ABI as the stack pointer and TLS base. There is no shipping WASIp3 target anywhere right now, so this breakage should not be observable. * Fix conditional builds * Add a note about restoration
1 parent 74d98bc commit fb8c761

8 files changed

Lines changed: 80 additions & 127 deletions

File tree

crates/cranelift/src/compiler/component.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,7 @@ impl<'a> TrampolineCompiler<'a> {
713713
|_, _| {},
714714
);
715715
}
716-
Trampoline::ThreadIndex => {
716+
Trampoline::ThreadIndex { .. } => {
717717
self.translate_libcall(
718718
host::thread_index,
719719
TrapSentinel::NegativeOne,
@@ -1478,7 +1478,6 @@ impl<'a> TrampolineCompiler<'a> {
14781478
let instance = match trampoline {
14791479
// These intrinsics explicitly do not check the may-leave flag.
14801480
Trampoline::ResourceRep { .. }
1481-
| Trampoline::ThreadIndex
14821481
| Trampoline::BackpressureInc { .. }
14831482
| Trampoline::BackpressureDec { .. } => return,
14841483

@@ -1508,6 +1507,7 @@ impl<'a> TrampolineCompiler<'a> {
15081507
| Trampoline::WaitableSetPoll { instance, .. }
15091508
| Trampoline::WaitableSetDrop { instance }
15101509
| Trampoline::WaitableJoin { instance }
1510+
| Trampoline::ThreadIndex { instance }
15111511
| Trampoline::ThreadNewIndirect { instance, .. }
15121512
| Trampoline::ThreadResumeLater { instance, .. }
15131513
| Trampoline::ThreadSuspend { instance, .. }

crates/environ/src/component/dfg.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,9 @@ pub enum Trampoline {
472472
Trap,
473473
EnterSyncCall,
474474
ExitSyncCall,
475-
ThreadIndex,
475+
ThreadIndex {
476+
instance: RuntimeComponentInstanceIndex,
477+
},
476478
ThreadNewIndirect {
477479
instance: RuntimeComponentInstanceIndex,
478480
start_func_ty_idx: ComponentTypeIndex,
@@ -1156,7 +1158,9 @@ impl LinearizeDfg<'_> {
11561158
Trampoline::Trap => info::Trampoline::Trap,
11571159
Trampoline::EnterSyncCall => info::Trampoline::EnterSyncCall,
11581160
Trampoline::ExitSyncCall => info::Trampoline::ExitSyncCall,
1159-
Trampoline::ThreadIndex => info::Trampoline::ThreadIndex,
1161+
Trampoline::ThreadIndex { instance } => info::Trampoline::ThreadIndex {
1162+
instance: *instance,
1163+
},
11601164
Trampoline::ThreadNewIndirect {
11611165
instance,
11621166
start_func_ty_idx,

crates/environ/src/component/info.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,7 +1109,10 @@ pub enum Trampoline {
11091109
ExitSyncCall,
11101110

11111111
/// Intrinsic used to implement the `thread.index` component model builtin.
1112-
ThreadIndex,
1112+
ThreadIndex {
1113+
/// The specific component instance which is calling the intrinsic.
1114+
instance: RuntimeComponentInstanceIndex,
1115+
},
11131116

11141117
/// Intrinsic used to implement the `thread.new-indirect` component model builtin.
11151118
ThreadNewIndirect {
@@ -1247,7 +1250,7 @@ impl Trampoline {
12471250
Trap => format!("trap"),
12481251
EnterSyncCall => format!("enter-sync-call"),
12491252
ExitSyncCall => format!("exit-sync-call"),
1250-
ThreadIndex => format!("thread-index"),
1253+
ThreadIndex { .. } => format!("thread-index"),
12511254
ThreadNewIndirect { .. } => format!("thread-new-indirect"),
12521255
ThreadResumeLater { .. } => format!("thread-resume-later"),
12531256
ThreadSuspend { .. } => format!("thread-suspend"),

crates/environ/src/component/translate/inline.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,10 +1092,12 @@ impl<'a> Inliner<'a> {
10921092
.push((*func, dfg::CoreDef::UnsafeIntrinsic(*func, intrinsic)));
10931093
}
10941094
ThreadIndex { func } => {
1095-
let index = self
1096-
.result
1097-
.trampolines
1098-
.push((*func, dfg::Trampoline::ThreadIndex));
1095+
let index = self.result.trampolines.push((
1096+
*func,
1097+
dfg::Trampoline::ThreadIndex {
1098+
instance: frame.instance,
1099+
},
1100+
));
10991101
frame.funcs.push((*func, dfg::CoreDef::Trampoline(index)));
11001102
}
11011103
ThreadNewIndirect {

crates/wasmtime/src/runtime/component/func/options.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,17 @@ impl<'a, T: 'static> LowerContext<'a, T> {
146146
) -> Result<usize> {
147147
assert!(self.allow_realloc);
148148

149+
// All calls to `realloc` options in the canonical ABI zero out the
150+
// `context.{get,set}` slots for the duration of the call. This sort of
151+
// fakes a "fresh thread" for each call, but this is the only observable
152+
// state so nothing else needs adjusting. Note though that the original
153+
// values are preserved still to get restored after this call.
154+
#[cfg(feature = "component-model-async")]
155+
let orig_context = core::mem::replace(
156+
self.store.0.vm_store_context_mut().component_context_mut(),
157+
Default::default(),
158+
);
159+
149160
let (component, store) = self.instance.component_and_store_mut(self.store.0);
150161
let instance = self.instance.id().get(store);
151162
let options = &component.env_component().options[self.options];
@@ -185,6 +196,14 @@ impl<'a, T: 'static> LowerContext<'a, T> {
185196
bail!("realloc return: beyond end of memory")
186197
}
187198

199+
// Note that this restoration isn't part of a `Drop` guard which works
200+
// because once a component traps it's locked-down and inaccessible, so
201+
// it's ok if this isn't restored.
202+
#[cfg(feature = "component-model-async")]
203+
{
204+
*self.store.0.vm_store_context_mut().component_context_mut() = orig_context;
205+
}
206+
188207
Ok(result)
189208
}
190209

tests/all/component_model/func.rs

Lines changed: 0 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -3150,110 +3150,6 @@ async fn thread_index_via_call(style: ApiStyle) -> Result<()> {
31503150
Ok(())
31513151
}
31523152

3153-
#[tokio::test]
3154-
async fn thread_index_via_post_return_sync() -> Result<()> {
3155-
thread_index_via_post_return(ApiStyle::Sync).await
3156-
}
3157-
3158-
#[tokio::test]
3159-
async fn thread_index_via_post_return_async() -> Result<()> {
3160-
thread_index_via_post_return(ApiStyle::Async).await
3161-
}
3162-
3163-
#[tokio::test]
3164-
async fn thread_index_via_post_return_concurrent() -> Result<()> {
3165-
thread_index_via_post_return(ApiStyle::Concurrent).await
3166-
}
3167-
3168-
async fn thread_index_via_post_return(style: ApiStyle) -> Result<()> {
3169-
let component = r#"
3170-
(component
3171-
(core module $m
3172-
(import "" "thread.index" (func $thread-index (result i32)))
3173-
(global $index (mut i32) (i32.const 0))
3174-
(func (export "run")
3175-
(global.set $index (call $thread-index))
3176-
(if (i32.eqz (global.get $index)) (then unreachable))
3177-
)
3178-
(func (export "run-post-return")
3179-
(local $index i32)
3180-
(local.set $index (call $thread-index))
3181-
(if (i32.eqz (local.get $index)) (then unreachable))
3182-
(if (i32.ne (local.get $index) (global.get $index)) (then unreachable))
3183-
)
3184-
)
3185-
(core func $thread-index (canon thread.index))
3186-
(core instance $m (instantiate $m (with "" (instance
3187-
(export "thread.index" (func $thread-index))
3188-
))))
3189-
(func (export "run") (canon lift (core func $m "run") (post-return (func $m "run-post-return"))))
3190-
)
3191-
"#;
3192-
let engine = Engine::new(&style.config())?;
3193-
let component = Component::new(&engine, component)?;
3194-
let mut store = Store::new(&engine, ());
3195-
let linker = Linker::new(&engine);
3196-
let instance = style.instantiate(&mut store, &linker, &component).await?;
3197-
let run = instance.get_typed_func::<(), ()>(&mut store, "run")?;
3198-
style.call(&mut store, run, ()).await?;
3199-
Ok(())
3200-
}
3201-
3202-
#[tokio::test]
3203-
async fn thread_index_via_cabi_realloc_sync() -> Result<()> {
3204-
thread_index_via_cabi_realloc(ApiStyle::Sync).await
3205-
}
3206-
3207-
#[tokio::test]
3208-
async fn thread_index_via_cabi_realloc_async() -> Result<()> {
3209-
thread_index_via_cabi_realloc(ApiStyle::Async).await
3210-
}
3211-
3212-
#[tokio::test]
3213-
async fn thread_index_via_cabi_realloc_concurrent() -> Result<()> {
3214-
thread_index_via_cabi_realloc(ApiStyle::Concurrent).await
3215-
}
3216-
3217-
async fn thread_index_via_cabi_realloc(style: ApiStyle) -> Result<()> {
3218-
let component = r#"
3219-
(component
3220-
(core module $m
3221-
(import "" "thread.index" (func $thread-index (result i32)))
3222-
(global $index (mut i32) (i32.const 0))
3223-
(memory (export "memory") 1)
3224-
(func (export "realloc") (param i32 i32 i32 i32) (result i32)
3225-
(global.set $index (call $thread-index))
3226-
(if (i32.eqz (global.get $index)) (then unreachable))
3227-
(i32.const 100)
3228-
)
3229-
(func (export "run") (param i32 i32)
3230-
(local $index i32)
3231-
(local.set $index (call $thread-index))
3232-
(if (i32.eqz (local.get $index)) (then unreachable))
3233-
(if (i32.ne (local.get $index) (global.get $index)) (then unreachable))
3234-
)
3235-
)
3236-
(core func $thread-index (canon thread.index))
3237-
(core instance $m (instantiate $m (with "" (instance
3238-
(export "thread.index" (func $thread-index))
3239-
))))
3240-
(func (export "run") (param "s" string) (canon lift
3241-
(core func $m "run")
3242-
(memory $m "memory")
3243-
(realloc (func $m "realloc"))
3244-
))
3245-
)
3246-
"#;
3247-
let engine = Engine::new(&style.config())?;
3248-
let component = Component::new(&engine, component)?;
3249-
let mut store = Store::new(&engine, ());
3250-
let linker = Linker::new(&engine);
3251-
let instance = style.instantiate(&mut store, &linker, &component).await?;
3252-
let run = instance.get_typed_func::<(String,), ()>(&mut store, "run")?;
3253-
style.call(&mut store, run, ("hola".to_string(),)).await?;
3254-
Ok(())
3255-
}
3256-
32573153
#[tokio::test]
32583154
async fn thread_index_via_resource_drop_sync() -> Result<()> {
32593155
thread_index_via_resource_drop(ApiStyle::Sync).await

tests/misc_testsuite/component-model-threading/threading-builtins.wast

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@
101101

102102
(assert_return (invoke "run") (u32.const 42))
103103

104-
;; Test that `thread.index` is exempt from may-leave checks
104+
;; Test that `thread.index` is not exempt from may-leave checks
105105
(component
106106
(core func $thread.index (canon thread.index))
107107

@@ -118,4 +118,33 @@
118118
(canon lift (core func $dm "run") (post-return (func $dm "post-return"))))
119119
)
120120

121-
(assert_return (invoke "run"))
121+
(assert_trap (invoke "run") "cannot leave component instance")
122+
123+
(component
124+
(core module $m
125+
(import "" "thread.index" (func $thread-index (result i32)))
126+
(global $index (mut i32) (i32.const 0))
127+
(memory (export "memory") 1)
128+
(func (export "realloc") (param i32 i32 i32 i32) (result i32)
129+
(global.set $index (call $thread-index))
130+
(if (i32.eqz (global.get $index)) (then unreachable))
131+
(i32.const 100)
132+
)
133+
(func (export "run") (param i32 i32)
134+
(local $index i32)
135+
(local.set $index (call $thread-index))
136+
(if (i32.eqz (local.get $index)) (then unreachable))
137+
(if (i32.ne (local.get $index) (global.get $index)) (then unreachable))
138+
)
139+
)
140+
(core func $thread-index (canon thread.index))
141+
(core instance $m (instantiate $m (with "" (instance
142+
(export "thread.index" (func $thread-index))
143+
))))
144+
(func (export "run") (param "s" string) (canon lift
145+
(core func $m "run")
146+
(memory $m "memory")
147+
(realloc (func $m "realloc"))
148+
))
149+
)
150+
(assert_trap (invoke "run" (str.const "x")) "cannot leave component instance")

tests/misc_testsuite/component-model/async/task-builtins.wast

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,8 @@
180180
call $backpressure.inc
181181
call $backpressure.dec
182182

183-
;; context.get should be what was set in `realloc`
184-
(if (i32.ne (call $context.get) (i32.const 100)) (then (unreachable)))
183+
;; context set in realloc should be separate from this thread's context
184+
(if (i32.ne (call $context.get) (i32.const 0)) (then (unreachable)))
185185
)
186186
)
187187
(core instance $m (instantiate $M (with "" (instance
@@ -416,7 +416,7 @@
416416
(if (i32.ne (local.get 2) (i32.const 1)) (then (unreachable)))
417417
(if (i32.ne (local.get 3) (i32.const 2)) (then (unreachable)))
418418

419-
(if (i32.ne (call $context.get) (i32.const 400)) (then (unreachable)))
419+
(if (i32.ne (call $context.get) (i32.const 0)) (then (unreachable)))
420420
(call $context.set (i32.const 500))
421421

422422
call $backpressure.inc
@@ -448,11 +448,11 @@
448448
(func (export "run")
449449
;; set this tasks's context before calling $run, in calling $run the
450450
;; runtime will then call `realloc` above for the string return value
451-
;; which should see our 400 value. That will then set 500 which we
452-
;; should then see after the return.
451+
;; which should NOT see our 400 value. That will then set 500 which we
452+
;; should NOT then see after the return.
453453
(call $context.set (i32.const 400))
454454
(call $run (i32.const 20))
455-
(if (i32.ne (call $context.get) (i32.const 500)) (then (unreachable)))
455+
(if (i32.ne (call $context.get) (i32.const 400)) (then (unreachable)))
456456
)
457457
)
458458
(core instance $m (instantiate $M (with "" (instance
@@ -616,7 +616,7 @@
616616
(if (i32.ne (local.get 3) (i32.const 2)) (then (unreachable)))
617617

618618
call $context.get
619-
i32.const 400
619+
i32.const 0
620620
i32.ne
621621
if unreachable end
622622

@@ -663,15 +663,15 @@
663663
(import "" "task.return" (func $task.return (param i32)))
664664
(import "" "memory" (memory 1))
665665

666-
;; Set context[0] to 400, then read the future, which should call realloc and set
667-
;; context[0] to 500, then check that we see that value.
666+
;; Set context[0] to 400, then read the future, which should call realloc
667+
;; and set context[0] to 500, then check that we DON'T see that value.
668668
(func (export "run-future") (param $fr i32) (result i32)
669669
(local $ret i32)
670670

671671
(call $context.set (i32.const 400))
672672
(local.set $ret (call $future.read (local.get $fr) (i32.const 40)))
673673
(if (i32.ne (i32.const 0 (; COMPLETED ;)) (local.get $ret)) (then (unreachable)))
674-
(if (i32.ne (call $context.get) (i32.const 500)) (then (unreachable)))
674+
(if (i32.ne (call $context.get) (i32.const 400)) (then (unreachable)))
675675

676676
(call $task.return (i32.const 42))
677677
(i32.const 0 (; EXIT ;))
@@ -684,7 +684,7 @@
684684
(call $context.set (i32.const 400))
685685
(local.set $ret (call $stream.read (local.get $sr) (i32.const 40) (i32.const 1)))
686686
(if (i32.ne (i32.const 0x10 (; COMPLETED | 1<<4 ;)) (local.get $ret)) (then (unreachable)))
687-
(if (i32.ne (call $context.get) (i32.const 500)) (then (unreachable)))
687+
(if (i32.ne (call $context.get) (i32.const 400)) (then (unreachable)))
688688

689689
(call $task.return (i32.const 42))
690690
(i32.const 0 (; EXIT ;))

0 commit comments

Comments
 (0)