From 28becbe6ee6783cdef3ee1faaa7e524414291c37 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sun, 19 Jul 2026 20:37:25 +0200 Subject: [PATCH] feat(compress): move CDict copy reset orchestration into Rust Move the prepared-CDict-to-CCtx reset sequence into a Rust projection with explicit reset, table, hashTable3, dictionary, and block-state ordering. C retains private layouts, table-tag removal, row-matchfinder metadata, and all pointer arithmetic behind callbacks, while reset allocation errors now stop the sequence before table mutation. Test Plan: - ulimit -v 41943040 && CARGO_BUILD_JOBS=1 cargo test --manifest-path rust/Cargo.toml (715 passed) - ulimit -v 41943040 && CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings - ulimit -v 41943040 && make -j1 - ulimit -v 41943040 && make -j1 -C tests test-fuzzer FUZZERTEST=-T3s FUZZER_FLAGS=--no-big-tests (305 cases) - ulimit -v 41943040 && make -j1 -C tests test-zstream ZSTREAM_TESTTIME=-T2s (84 deterministic plus 248 randomized cases) --- lib/compress/zstd_compress.c | 201 ++++++++++++++++----- rust/src/zstd_compress_dictionary.rs | 260 +++++++++++++++++++++++++++ 2 files changed, 415 insertions(+), 46 deletions(-) diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 201066d76..c14e2d532 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -2361,6 +2361,46 @@ typedef char ZSTD_rust_reset_cctx_using_cdict_state_layout[ == 10 * sizeof(void*) && sizeof(ZSTD_rust_resetCCtxUsingCDictState) == 11 * sizeof(void*)) ? 1 : -1]; +typedef size_t (*ZSTD_rust_resetCCtxByCopyingCDictReset_f)( + void* context, const void* cdict, const void* params, + U64 pledgedSrcSize, int zbuff); +typedef void (*ZSTD_rust_resetCCtxByCopyingCDictState_f)( + void* context, const void* cdict); +typedef void (*ZSTD_rust_resetCCtxByCopyingCDictMarkTables_f)(void* context); +typedef struct { + void* callbackContext; + const void* cdict; + const void* params; + U64 pledgedSrcSize; + ZSTD_rust_resetCCtxByCopyingCDictReset_f reset; + ZSTD_rust_resetCCtxByCopyingCDictMarkTables_f markTablesDirty; + ZSTD_rust_resetCCtxByCopyingCDictState_f copyTables; + ZSTD_rust_resetCCtxByCopyingCDictState_f zeroHashTable3; + ZSTD_rust_resetCCtxByCopyingCDictMarkTables_f markTablesClean; + ZSTD_rust_resetCCtxByCopyingCDictState_f copyMatchState; + ZSTD_rust_resetCCtxByCopyingCDictState_f copyDictState; + ZSTD_rust_resetCCtxByCopyingCDictState_f copyBlockState; + int zbuff; +} ZSTD_rust_resetCCtxByCopyingCDictState; +size_t ZSTD_rust_resetCCtxByCopyingCDict( + const ZSTD_rust_resetCCtxByCopyingCDictState* state); +typedef char ZSTD_rust_reset_cctx_by_copying_cdict_state_layout[ + (offsetof(ZSTD_rust_resetCCtxByCopyingCDictState, callbackContext) == 0 + && offsetof(ZSTD_rust_resetCCtxByCopyingCDictState, cdict) + == sizeof(void*) + && offsetof(ZSTD_rust_resetCCtxByCopyingCDictState, params) + == 2 * sizeof(void*) + && offsetof(ZSTD_rust_resetCCtxByCopyingCDictState, pledgedSrcSize) + == 3 * sizeof(void*) + && offsetof(ZSTD_rust_resetCCtxByCopyingCDictState, reset) + == 3 * sizeof(void*) + sizeof(U64) + && offsetof(ZSTD_rust_resetCCtxByCopyingCDictState, zbuff) + == 3 * sizeof(void*) + sizeof(U64) + 8 * sizeof(void*) + && sizeof(ZSTD_rust_resetCCtxByCopyingCDictState) + == ((offsetof(ZSTD_rust_resetCCtxByCopyingCDictState, zbuff) + + sizeof(int) + sizeof(void*) - 1) / sizeof(void*)) + * sizeof(void*)) + ? 1 : -1]; /* The sequence-compression loop receives only the state it actually reads or * updates. In particular, neither ZSTD_CCtx nor a C function pointer crosses @@ -4319,17 +4359,19 @@ static void ZSTD_copyCDictTableIntoCCtx(U32* dst, U32 const* src, size_t tableSi ZSTD_CDictIndicesAreTagged(cParams)); } -static size_t ZSTD_resetCCtx_byCopyingCDict(ZSTD_CCtx* cctx, - const ZSTD_CDict* cdict, - ZSTD_CCtx_params params, - U64 pledgedSrcSize, - ZSTD_buffered_policy_e zbuff) +static size_t ZSTD_rust_resetCCtx_byCopyingCDict_reset( + void* context, const void* cdictOpaque, const void* paramsOpaque, + U64 pledgedSrcSize, int zbuff) { - const ZSTD_compressionParameters *cdict_cParams = &cdict->matchState.cParams; + ZSTD_CCtx* const cctx = (ZSTD_CCtx*)context; + const ZSTD_CDict* const cdict = (const ZSTD_CDict*)cdictOpaque; + ZSTD_CCtx_params params = *(const ZSTD_CCtx_params*)paramsOpaque; + const ZSTD_compressionParameters* const cdict_cParams = + &cdict->matchState.cParams; assert(!cdict->matchState.dedicatedDictSearch); DEBUGLOG(4, "ZSTD_resetCCtx_byCopyingCDict() pledgedSrcSize=%llu", - (unsigned long long)pledgedSrcSize); + (unsigned long long)pledgedSrcSize); { unsigned const windowLog = params.cParams.windowLog; assert(windowLog != 0); @@ -4337,68 +4379,135 @@ static size_t ZSTD_resetCCtx_byCopyingCDict(ZSTD_CCtx* cctx, params.cParams = *cdict_cParams; params.cParams.windowLog = windowLog; params.useRowMatchFinder = cdict->useRowMatchFinder; - FORWARD_IF_ERROR(ZSTD_resetCCtx_internal(cctx, ¶ms, pledgedSrcSize, - /* loadedDictSize */ 0, - ZSTDcrp_leaveDirty, zbuff), ""); + { size_t const resetError = ZSTD_resetCCtx_internal( + cctx, ¶ms, pledgedSrcSize, + /* loadedDictSize */ 0, + ZSTDcrp_leaveDirty, + (ZSTD_buffered_policy_e)zbuff); + if (ZSTD_isError(resetError)) return resetError; + } assert(cctx->appliedParams.cParams.strategy == cdict_cParams->strategy); assert(cctx->appliedParams.cParams.hashLog == cdict_cParams->hashLog); assert(cctx->appliedParams.cParams.chainLog == cdict_cParams->chainLog); } + return 0; +} +static void ZSTD_rust_resetCCtx_byCopyingCDict_mark_tables_dirty(void* context) +{ + ZSTD_CCtx* const cctx = (ZSTD_CCtx*)context; ZSTD_cwksp_mark_tables_dirty(&cctx->workspace); - assert(params.useRowMatchFinder != ZSTD_ps_auto); +} - /* copy tables */ - { size_t const chainSize = ZSTD_allocateChainTable(cdict_cParams->strategy, cdict->useRowMatchFinder, 0 /* DDS guaranteed disabled */) - ? ((size_t)1 << cdict_cParams->chainLog) - : 0; - size_t const hSize = (size_t)1 << cdict_cParams->hashLog; +static void ZSTD_rust_resetCCtx_byCopyingCDict_copy_tables( + void* context, const void* cdictOpaque) +{ + ZSTD_CCtx* const cctx = (ZSTD_CCtx*)context; + const ZSTD_CDict* const cdict = (const ZSTD_CDict*)cdictOpaque; + const ZSTD_compressionParameters* const cdict_cParams = + &cdict->matchState.cParams; + size_t const chainSize = ZSTD_allocateChainTable( + cdict_cParams->strategy, cdict->useRowMatchFinder, + 0 /* DDS guaranteed disabled */) + ? ((size_t)1 << cdict_cParams->chainLog) + : 0; + size_t const hSize = (size_t)1 << cdict_cParams->hashLog; - ZSTD_copyCDictTableIntoCCtx(cctx->blockState.matchState.hashTable, + ZSTD_copyCDictTableIntoCCtx(cctx->blockState.matchState.hashTable, cdict->matchState.hashTable, hSize, cdict_cParams); - /* Do not copy cdict's chainTable if cctx has parameters such that it would not use chainTable */ - if (ZSTD_allocateChainTable(cctx->appliedParams.cParams.strategy, cctx->appliedParams.useRowMatchFinder, 0 /* forDDSDict */)) { - ZSTD_copyCDictTableIntoCCtx(cctx->blockState.matchState.chainTable, + /* Do not copy cdict's chainTable if cctx will not use a chainTable. */ + if (ZSTD_allocateChainTable(cctx->appliedParams.cParams.strategy, + cctx->appliedParams.useRowMatchFinder, + 0 /* forDDSDict */)) { + ZSTD_copyCDictTableIntoCCtx(cctx->blockState.matchState.chainTable, cdict->matchState.chainTable, chainSize, cdict_cParams); - } - /* copy tag table */ - if (ZSTD_rowMatchFinderUsed(cdict_cParams->strategy, cdict->useRowMatchFinder)) { - size_t const tagTableSize = hSize; - ZSTD_memcpy(cctx->blockState.matchState.tagTable, - cdict->matchState.tagTable, - tagTableSize); - cctx->blockState.matchState.hashSalt = cdict->matchState.hashSalt; - } } - - /* Zero the hashTable3, since the cdict never fills it */ - assert(cctx->blockState.matchState.hashLog3 <= 31); - { U32 const h3log = cctx->blockState.matchState.hashLog3; - size_t const h3Size = h3log ? ((size_t)1 << h3log) : 0; - assert(cdict->matchState.hashLog3 == 0); - ZSTD_memset(cctx->blockState.matchState.hashTable3, 0, h3Size * sizeof(U32)); + if (ZSTD_rowMatchFinderUsed(cdict_cParams->strategy, + cdict->useRowMatchFinder)) { + ZSTD_memcpy(cctx->blockState.matchState.tagTable, + cdict->matchState.tagTable, hSize); + cctx->blockState.matchState.hashSalt = cdict->matchState.hashSalt; } +} +static void ZSTD_rust_resetCCtx_byCopyingCDict_zero_hash_table3( + void* context, const void* cdictOpaque) +{ + ZSTD_CCtx* const cctx = (ZSTD_CCtx*)context; + const ZSTD_CDict* const cdict = (const ZSTD_CDict*)cdictOpaque; + U32 const h3log = cctx->blockState.matchState.hashLog3; + assert(h3log <= 31); + assert(cdict->matchState.hashLog3 == 0); + ZSTD_memset(cctx->blockState.matchState.hashTable3, 0, + (h3log ? ((size_t)1 << h3log) : 0) * sizeof(U32)); +} + +static void ZSTD_rust_resetCCtx_byCopyingCDict_mark_tables_clean(void* context) +{ + ZSTD_CCtx* const cctx = (ZSTD_CCtx*)context; ZSTD_cwksp_mark_tables_clean(&cctx->workspace); +} - /* copy dictionary offsets */ - { ZSTD_MatchState_t const* srcMatchState = &cdict->matchState; - ZSTD_MatchState_t* dstMatchState = &cctx->blockState.matchState; - dstMatchState->window = srcMatchState->window; - dstMatchState->nextToUpdate = srcMatchState->nextToUpdate; - dstMatchState->loadedDictEnd= srcMatchState->loadedDictEnd; - } +static void ZSTD_rust_resetCCtx_byCopyingCDict_copy_match_state( + void* context, const void* cdictOpaque) +{ + ZSTD_CCtx* const cctx = (ZSTD_CCtx*)context; + const ZSTD_CDict* const cdict = (const ZSTD_CDict*)cdictOpaque; + const ZSTD_MatchState_t* const src = &cdict->matchState; + ZSTD_MatchState_t* const dst = &cctx->blockState.matchState; + dst->window = src->window; + dst->nextToUpdate = src->nextToUpdate; + dst->loadedDictEnd = src->loadedDictEnd; +} +static void ZSTD_rust_resetCCtx_byCopyingCDict_copy_dict_state( + void* context, const void* cdictOpaque) +{ + ZSTD_CCtx* const cctx = (ZSTD_CCtx*)context; + const ZSTD_CDict* const cdict = (const ZSTD_CDict*)cdictOpaque; cctx->dictID = cdict->dictID; cctx->dictContentSize = cdict->dictContentSize; +} - /* copy block state */ - ZSTD_memcpy(cctx->blockState.prevCBlock, &cdict->cBlockState, sizeof(cdict->cBlockState)); +static void ZSTD_rust_resetCCtx_byCopyingCDict_copy_block_state( + void* context, const void* cdictOpaque) +{ + ZSTD_CCtx* const cctx = (ZSTD_CCtx*)context; + const ZSTD_CDict* const cdict = (const ZSTD_CDict*)cdictOpaque; + ZSTD_memcpy(cctx->blockState.prevCBlock, &cdict->cBlockState, + sizeof(cdict->cBlockState)); +} - return 0; +static size_t ZSTD_resetCCtx_byCopyingCDict(ZSTD_CCtx* cctx, + const ZSTD_CDict* cdict, + ZSTD_CCtx_params params, + U64 pledgedSrcSize, + ZSTD_buffered_policy_e zbuff) +{ + ZSTD_rust_resetCCtxByCopyingCDictState state; + state.callbackContext = cctx; + state.cdict = cdict; + state.params = ¶ms; + state.pledgedSrcSize = pledgedSrcSize; + state.reset = ZSTD_rust_resetCCtx_byCopyingCDict_reset; + state.markTablesDirty = + ZSTD_rust_resetCCtx_byCopyingCDict_mark_tables_dirty; + state.copyTables = ZSTD_rust_resetCCtx_byCopyingCDict_copy_tables; + state.zeroHashTable3 = + ZSTD_rust_resetCCtx_byCopyingCDict_zero_hash_table3; + state.markTablesClean = + ZSTD_rust_resetCCtx_byCopyingCDict_mark_tables_clean; + state.copyMatchState = + ZSTD_rust_resetCCtx_byCopyingCDict_copy_match_state; + state.copyDictState = + ZSTD_rust_resetCCtx_byCopyingCDict_copy_dict_state; + state.copyBlockState = + ZSTD_rust_resetCCtx_byCopyingCDict_copy_block_state; + state.zbuff = (int)zbuff; + return ZSTD_rust_resetCCtxByCopyingCDict(&state); } static size_t ZSTD_rust_resetCCtxUsingCDict_attach( diff --git a/rust/src/zstd_compress_dictionary.rs b/rust/src/zstd_compress_dictionary.rs index f0025259e..0ad18f666 100644 --- a/rust/src/zstd_compress_dictionary.rs +++ b/rust/src/zstd_compress_dictionary.rs @@ -1294,6 +1294,117 @@ pub unsafe extern "C" fn ZSTD_rust_resetCCtxUsingCDict( } } +type ResetCCtxByCopyingCDictResetFn = + unsafe extern "C" fn(*mut c_void, *const c_void, *const c_void, u64, c_int) -> usize; +type ResetCCtxByCopyingCDictStateFn = unsafe extern "C" fn(*mut c_void, *const c_void); +type ResetCCtxByCopyingCDictMarkTablesFn = unsafe extern "C" fn(*mut c_void); + +/// Projection for copying a prepared CDict into a working CCtx. +/// +/// Rust owns the reset/copy ordering and stops on reset allocation failure. +/// C retains the private CCtx/CDict layout, table-size policy, and pointer +/// arithmetic behind callbacks. +#[repr(C)] +pub struct ZSTD_rust_resetCCtxByCopyingCDictState { + callback_context: *mut c_void, + cdict: *const c_void, + params: *const c_void, + pledged_src_size: u64, + reset: Option, + mark_tables_dirty: Option, + copy_tables: Option, + zero_hash_table3: Option, + mark_tables_clean: Option, + copy_match_state: Option, + copy_dict_state: Option, + copy_block_state: Option, + zbuff: c_int, +} + +const _: () = { + assert!(size_of::() == size_of::()); + assert!(size_of::() == size_of::()); + assert!(size_of::() == size_of::()); + assert!(offset_of!(ZSTD_rust_resetCCtxByCopyingCDictState, callback_context) == 0); + assert!(offset_of!(ZSTD_rust_resetCCtxByCopyingCDictState, cdict) == size_of::()); + assert!(offset_of!(ZSTD_rust_resetCCtxByCopyingCDictState, params) == 2 * size_of::()); + assert!( + offset_of!(ZSTD_rust_resetCCtxByCopyingCDictState, pledged_src_size) + == 3 * size_of::() + ); + assert!( + offset_of!(ZSTD_rust_resetCCtxByCopyingCDictState, reset) + == 3 * size_of::() + size_of::() + ); + assert!( + offset_of!(ZSTD_rust_resetCCtxByCopyingCDictState, zbuff) + == 3 * size_of::() + size_of::() + size_of::<[usize; 8]>() + ); + assert!( + size_of::() + == (offset_of!(ZSTD_rust_resetCCtxByCopyingCDictState, zbuff) + size_of::()) + .div_ceil(size_of::()) + * size_of::() + ); +}; + +/// Run the private CDict-copy operation through C-owned layout callbacks. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_resetCCtxByCopyingCDict( + state: *const ZSTD_rust_resetCCtxByCopyingCDictState, +) -> usize { + if state.is_null() { + return ERROR(ZstdErrorCode::Generic); + } + let state = unsafe { &*state }; + let ( + Some(reset), + Some(mark_tables_dirty), + Some(copy_tables), + Some(zero_hash_table3), + Some(mark_tables_clean), + Some(copy_match_state), + Some(copy_dict_state), + Some(copy_block_state), + ) = ( + state.reset, + state.mark_tables_dirty, + state.copy_tables, + state.zero_hash_table3, + state.mark_tables_clean, + state.copy_match_state, + state.copy_dict_state, + state.copy_block_state, + ) + else { + return ERROR(ZstdErrorCode::Generic); + }; + if state.callback_context.is_null() || state.cdict.is_null() || state.params.is_null() { + return ERROR(ZstdErrorCode::Generic); + } + + unsafe { + let reset_error = reset( + state.callback_context, + state.cdict, + state.params, + state.pledged_src_size, + state.zbuff, + ); + if ERR_isError(reset_error) { + return reset_error; + } + mark_tables_dirty(state.callback_context); + copy_tables(state.callback_context, state.cdict); + zero_hash_table3(state.callback_context, state.cdict); + mark_tables_clean(state.callback_context); + copy_match_state(state.callback_context, state.cdict); + copy_dict_state(state.callback_context, state.cdict); + copy_block_state(state.callback_context, state.cdict); + } + 0 +} + /// Scalar projections for the public CDict query helpers. #[repr(C)] pub struct ZSTD_rust_cdictQueryState { @@ -2416,6 +2527,155 @@ mod tests { assert_eq!(probe.events, ["attach"]); } + #[derive(Default)] + struct ResetCCtxByCopyingCDictProbe { + events: Vec<&'static str>, + reset_result: usize, + cdict: *const c_void, + params: *const c_void, + pledged_src_size: u64, + zbuff: c_int, + } + + unsafe fn reset_cctx_by_copying_cdict_probe( + context: *mut c_void, + ) -> &'static mut ResetCCtxByCopyingCDictProbe { + unsafe { &mut *context.cast::() } + } + + unsafe extern "C" fn reset_cctx_by_copying_cdict_reset( + context: *mut c_void, + cdict: *const c_void, + params: *const c_void, + pledged_src_size: u64, + zbuff: c_int, + ) -> usize { + let probe = unsafe { reset_cctx_by_copying_cdict_probe(context) }; + probe.events.push("reset"); + probe.cdict = cdict; + probe.params = params; + probe.pledged_src_size = pledged_src_size; + probe.zbuff = zbuff; + probe.reset_result + } + + unsafe extern "C" fn reset_cctx_by_copying_cdict_mark_dirty(context: *mut c_void) { + unsafe { reset_cctx_by_copying_cdict_probe(context) } + .events + .push("dirty"); + } + + unsafe extern "C" fn reset_cctx_by_copying_cdict_copy_tables( + context: *mut c_void, + _cdict: *const c_void, + ) { + unsafe { reset_cctx_by_copying_cdict_probe(context) } + .events + .push("tables"); + } + + unsafe extern "C" fn reset_cctx_by_copying_cdict_zero_hash_table3( + context: *mut c_void, + _cdict: *const c_void, + ) { + unsafe { reset_cctx_by_copying_cdict_probe(context) } + .events + .push("zero-h3"); + } + + unsafe extern "C" fn reset_cctx_by_copying_cdict_mark_clean(context: *mut c_void) { + unsafe { reset_cctx_by_copying_cdict_probe(context) } + .events + .push("clean"); + } + + unsafe extern "C" fn reset_cctx_by_copying_cdict_copy_match_state( + context: *mut c_void, + _cdict: *const c_void, + ) { + unsafe { reset_cctx_by_copying_cdict_probe(context) } + .events + .push("match"); + } + + unsafe extern "C" fn reset_cctx_by_copying_cdict_copy_dict_state( + context: *mut c_void, + _cdict: *const c_void, + ) { + unsafe { reset_cctx_by_copying_cdict_probe(context) } + .events + .push("dict"); + } + + unsafe extern "C" fn reset_cctx_by_copying_cdict_copy_block_state( + context: *mut c_void, + _cdict: *const c_void, + ) { + unsafe { reset_cctx_by_copying_cdict_probe(context) } + .events + .push("block"); + } + + fn reset_cctx_by_copying_cdict_test_state( + probe: &mut ResetCCtxByCopyingCDictProbe, + cdict: *const c_void, + params: *const c_void, + ) -> ZSTD_rust_resetCCtxByCopyingCDictState { + ZSTD_rust_resetCCtxByCopyingCDictState { + callback_context: (probe as *mut ResetCCtxByCopyingCDictProbe).cast(), + cdict, + params, + pledged_src_size: 123, + reset: Some(reset_cctx_by_copying_cdict_reset), + mark_tables_dirty: Some(reset_cctx_by_copying_cdict_mark_dirty), + copy_tables: Some(reset_cctx_by_copying_cdict_copy_tables), + zero_hash_table3: Some(reset_cctx_by_copying_cdict_zero_hash_table3), + mark_tables_clean: Some(reset_cctx_by_copying_cdict_mark_clean), + copy_match_state: Some(reset_cctx_by_copying_cdict_copy_match_state), + copy_dict_state: Some(reset_cctx_by_copying_cdict_copy_dict_state), + copy_block_state: Some(reset_cctx_by_copying_cdict_copy_block_state), + zbuff: 7, + } + } + + #[test] + fn reset_cctx_by_copying_cdict_runs_callbacks_in_original_order() { + let mut probe = ResetCCtxByCopyingCDictProbe::default(); + let cdict = 0x4000usize as *const c_void; + let params = 0x3000usize as *const c_void; + let state = reset_cctx_by_copying_cdict_test_state(&mut probe, cdict, params); + + let result = unsafe { ZSTD_rust_resetCCtxByCopyingCDict(&state) }; + + assert_eq!(result, 0); + assert_eq!( + probe.events, + ["reset", "dirty", "tables", "zero-h3", "clean", "match", "dict", "block"] + ); + assert_eq!(probe.cdict, cdict); + assert_eq!(probe.params, params); + assert_eq!(probe.pledged_src_size, 123); + assert_eq!(probe.zbuff, 7); + } + + #[test] + fn reset_cctx_by_copying_cdict_stops_after_reset_error() { + let mut probe = ResetCCtxByCopyingCDictProbe { + reset_result: ERROR(ZstdErrorCode::MemoryAllocation), + ..Default::default() + }; + let state = reset_cctx_by_copying_cdict_test_state( + &mut probe, + 0x4000usize as *const c_void, + 0x3000usize as *const c_void, + ); + + let result = unsafe { ZSTD_rust_resetCCtxByCopyingCDict(&state) }; + + assert_eq!(result, ERROR(ZstdErrorCode::MemoryAllocation)); + assert_eq!(probe.events, ["reset"]); + } + unsafe fn cctx_policy_probe(context: *mut c_void) -> &'static mut CctxPolicyProbe { unsafe { &mut *context.cast::() } }