#12344·swc

`compress.inline` changes memory semantics for closures

Author: lukebrodyCreated Sep 11, 2026Updated Sep 11, 2026
LabelsC-bug

Describe the bug

Problem

See this example

In the example above, running the original code (in Node 24.11.1):

undefined

running the code after inlining:

{ weak: true }

The inlining combines the scope of the narrowing function with that of its caller.

This is because v8 creates one context per scope (not one per closure).

Since the context retains everything in scope for all of its closures, if the caller also declares closures, the variables captured by those closures are now included in the callee's closure's context.

Impact

This makes the usage of narrowing factory functions to reduce closure retaining problematic when also using inlining.

Functions can be exempted from inlining using directives, but recognizing that inlining is a problem in these cases is an unintuitive stumbling block for programmers.

Because engine issues are unlikely to be solved in browsers (see referenced issues in this writeup: https://jakearchibald.com/2024/garbage-collection-and-closures/), programmers must be able to optimize closure memory usage.

Possible Solutions

  • Don't inline a function whose body creates a closure into a scope that already creates one.
  • Disable inlining by default. (In our project, the code size impact is negligible)

Input code

typescript
function makeNarrow(small) {
    return x => small(x)
}

export function outer(state) {
    const wide = () => state.other
    // when makeNarrow is inlined, its return value uses `outer`'s context
    // v8 uses the same context for all closures declared in a scope,
    // so `state` referenced by `wide` is included in the context of makeNarrow's return value 
    return makeNarrow(state.small)
}

const small = 1
const other = new WeakRef({ weak: true })

globalThis.res = outer({ small, other: other.deref() })

setTimeout(() => {
    global.gc()
    console.log(other.deref())
}, 100)

Config

json
{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "jsx": false
    },
    "target": "es5",
    "loose": false,
    "minify": {
      "compress": {
        "inline": 1,
        "arguments": false,
        "arrows": false,
        "booleans": false,
        "booleans_as_integers": false,
        "collapse_vars": false,
        "comparisons": false,
        "computed_props": false,
        "conditionals": false,
        "dead_code": false,
        "directives": false,
        "drop_console": false,
        "drop_debugger": false,
        "evaluate": false,
        "expression": false,
        "hoist_funs": false,
        "hoist_props": false,
        "hoist_vars": false,
        "if_return": false,
        "join_vars": false,
        "keep_classnames": false,
        "keep_fargs": false,
        "keep_fnames": false,
        "keep_infinity": false,
        "loops": false,
        "negate_iife": false,
        "properties": false,
        "reduce_funcs": false,
        "reduce_vars": false,
        "side_effects": false,
        "switches": false,
        "typeofs": false,
        "unsafe": false,
        "unsafe_arrows": false,
        "unsafe_comps": false,
        "unsafe_Function": false,
        "unsafe_math": false,
        "unsafe_symbols": false,
        "unsafe_methods": false,
        "unsafe_proto": false,
        "unsafe_regexp": false,
        "unsafe_undefined": false,
        "unused": false,
        "const_to_let": false,
        "pristine_globals": false
      },
      "mangle": false
    }
  },
  "module": {
    "type": "es6"
  },
  "minify": false,
  "isModule": true
}

Link to the code that reproduces this issue

https://play.swc.rs/?version=1.16.2&code=H4sIAAAAAAAAA11RQW7DIBC8%2BxV7C5YsJ7lVkdIn9FBF6tUELwkKhghw7MrK37tAqNz6glc7OzM7K0cjgrIGBn7DD%2B6cnZgfuNY1LBXQ5zCMzsAMx3dIDTbX1bOqcL5bF0CWeTsGdMwHHrCMCmt8gEn1CEdgdWKI%2FdaGK7oE2W5huuJaHZQHZbQy2Deggi8GHlyPCKNHD13S6jY%2BKgScQ6F6vGUA0YPnA5Y%2BSOuArIPQ1o%2BOED0KzR32JAUcvLB3bAqLt9Alnx1pS3RoBAHP39DFVbpsUOixz%2BNRrOhYudpk88%2F7Os512imSnHkMNqeWaopt%2F6pTZFQbnOAL%2Be0TJVtgor8DBEfsz7qqLtqeuT5dlW%2FjksfXUZbM1mSSQ37anjaTdJU46DGc1IAEZ%2FlO%2BYCZr70IVv8e1Gpstb2wPyRkvIH9blf%2FAOr5whxPAgAA&config=H4sIAAAAAAAAA32VPZLbMAyFe5%2FCo3qbFEmxB0i3Z%2BDQIijDK5IaAvRas%2BO7B%2FqznQRUZ%2BF7EMRHAP4%2BHI%2FNhdrm%2FfgtP%2BVhsJkgP54lQmNke5NIA22w1GYcuHnb6IUm5G1PMIfuC2nY5g54zqKfq7zpUyLY5GssYEQ%2FvhZsUxgyEL3EJIqxxzgl%2F3h7BqVICRCZ%2Fn7pynL60sAppR5s3EPGksHI0EHWZG3qezsQmKut8CA%2BIiW1yEQLgzNDToMuiA4ZU5S4gh1YZ9rkQGOYoWW8gpoo9SQxkhyyhh2cStfNHfAvh6vti2UtFW7zjcknK%2FCckNj4opqxwJoTC624jN5k4JK1mpeEsZb3CSA29JYo2qAaNSu8NFcd7qZi9NLVPCpcRkA9aYROrDWIXvN38gcyo1oygystTAa3O7hiBqEDA95L16j4C7k9q2V5HCB5jchNW%2FUYCzDVyVz5NCA7%2BLcclPVWWxXB8rlOaQynpE7Wlg58Tm5HINfBqY6z7I3bUOclOpAGAadKCqlgmls2nEw%2Fr9X%2FO0QmRd5puj6dXvbGqrg%2FFnawsXuO%2F7KzD6ugCcmVGa7%2FBtMdLzv8V%2FMUbRv78Q0N0seWybnA4f4HDHlm0lkGAAA%3D

SWC Info output

No response

Expected behavior

The code, with minification, outputs undefined

Actual behavior

The code, with minification, outputs { weak: true }

Version

1.16.2

Additional context

No response