Skip to content

ASYNCIFY=1 unnecessarily generates many temporary locals #22196

@kichikuou

Description

@kichikuou

-s ASYNCIFY=1 in Emscripten 3.1.60 and later generates more local variables than before.

test.c:

#include <emscripten.h>

EM_ASYNC_JS(int, some_async_func, (int x), {
    return x;
});

int main() {
    int a = 0;
    a = some_async_func(a);
    a = some_async_func(a);
    a = some_async_func(a);
    a = some_async_func(a);
    a = some_async_func(a);
    return a;
}

Command line to build:

emcc -O3 -sASYNCIFY=1 -o test.js test.c

main() compiled with emscripten 3.1.60 has two params and six locals, where locals 3 through 7 are used only once each to temporarily hold the result of each some_async_func().

`main()` compiled with emscripten 3.1.60
  (func (;10;) (type 3) (param i32 i32) (result i32)
    (local i32 i32 i32 i32 i32 i32)
    global.get 1
    i32.const 2
    i32.eq
    if  ;; label = @1
      global.get 2
      global.get 2
      i32.load
      i32.const 4
      i32.sub
      i32.store
      global.get 2
      i32.load
      i32.load
      local.set 0
    end
    block (result i32)  ;; label = @1
      global.get 1
      i32.const 2
      i32.eq
      if  ;; label = @2
        global.get 2
        global.get 2
        i32.load
        i32.const 4
        i32.sub
        i32.store
        global.get 2
        i32.load
        i32.load
        local.set 2
      end
      global.get 1
      i32.eqz
      local.get 2
      i32.eqz
      i32.or
      if  ;; label = @2
        i32.const 0
        call 0
        local.set 3
        i32.const 0
        global.get 1
        i32.const 1
        i32.eq
        br_if 1 (;@1;)
        drop
        local.get 3
        local.set 0
      end
      global.get 1
      i32.eqz
      local.get 2
      i32.const 1
      i32.eq
      i32.or
      if  ;; label = @2
        local.get 0
        call 0
        local.set 4
        i32.const 1
        global.get 1
        i32.const 1
        i32.eq
        br_if 1 (;@1;)
        drop
        local.get 4
        local.set 0
      end
      global.get 1
      i32.eqz
      local.get 2
      i32.const 2
      i32.eq
      i32.or
      if  ;; label = @2
        local.get 0
        call 0
        local.set 5
        i32.const 2
        global.get 1
        i32.const 1
        i32.eq
        br_if 1 (;@1;)
        drop
        local.get 5
        local.set 0
      end
      global.get 1
      i32.eqz
      local.get 2
      i32.const 3
      i32.eq
      i32.or
      if  ;; label = @2
        local.get 0
        call 0
        local.set 6
        i32.const 3
        global.get 1
        i32.const 1
        i32.eq
        br_if 1 (;@1;)
        drop
        local.get 6
        local.set 0
      end
      global.get 1
      i32.eqz
      local.get 2
      i32.const 4
      i32.eq
      i32.or
      if  ;; label = @2
        local.get 0
        call 0
        local.set 7
        i32.const 4
        global.get 1
        i32.const 1
        i32.eq
        br_if 1 (;@1;)
        drop
        local.get 7
        local.set 0
      end
      global.get 1
      i32.eqz
      if  ;; label = @2
        local.get 0
        return
      end
      unreachable
    end
    local.set 1
    global.get 2
    i32.load
    local.get 1
    i32.store
    global.get 2
    global.get 2
    i32.load
    i32.const 4
    i32.add
    i32.store
    global.get 2
    i32.load
    local.get 0
    i32.store
    global.get 2
    global.get 2
    i32.load
    i32.const 4
    i32.add
    i32.store
    i32.const 0)

When built with Emscripten 3.1.59, these temp locals are coalesced and main() has only one local.

`main()` compiled with emscripten 3.1.59
  (func (;10;) (type 3) (param i32 i32) (result i32)
    (local i32)
    global.get 1
    i32.const 2
    i32.eq
    if  ;; label = @1
      global.get 2
      global.get 2
      i32.load
      i32.const 4
      i32.sub
      i32.store
      global.get 2
      i32.load
      i32.load
      local.set 0
    end
    block (result i32)  ;; label = @1
      global.get 1
      i32.const 2
      i32.eq
      if  ;; label = @2
        global.get 2
        global.get 2
        i32.load
        i32.const 4
        i32.sub
        i32.store
        global.get 2
        i32.load
        i32.load
        local.set 2
      end
      global.get 1
      i32.eqz
      local.get 2
      i32.eqz
      i32.or
      if  ;; label = @2
        i32.const 0
        call 0
        local.set 1
        i32.const 0
        global.get 1
        i32.const 1
        i32.eq
        br_if 1 (;@1;)
        drop
        local.get 1
        local.set 0
      end
      global.get 1
      i32.eqz
      local.get 2
      i32.const 1
      i32.eq
      i32.or
      if  ;; label = @2
        local.get 0
        call 0
        local.set 1
        i32.const 1
        global.get 1
        i32.const 1
        i32.eq
        br_if 1 (;@1;)
        drop
        local.get 1
        local.set 0
      end
      global.get 1
      i32.eqz
      local.get 2
      i32.const 2
      i32.eq
      i32.or
      if  ;; label = @2
        local.get 0
        call 0
        local.set 1
        i32.const 2
        global.get 1
        i32.const 1
        i32.eq
        br_if 1 (;@1;)
        drop
        local.get 1
        local.set 0
      end
      global.get 1
      i32.eqz
      local.get 2
      i32.const 3
      i32.eq
      i32.or
      if  ;; label = @2
        local.get 0
        call 0
        local.set 1
        i32.const 3
        global.get 1
        i32.const 1
        i32.eq
        br_if 1 (;@1;)
        drop
        local.get 1
        local.set 0
      end
      global.get 1
      i32.eqz
      local.get 2
      i32.const 4
      i32.eq
      i32.or
      if  ;; label = @2
        local.get 0
        call 0
        local.set 1
        i32.const 4
        global.get 1
        i32.const 1
        i32.eq
        br_if 1 (;@1;)
        drop
        local.get 1
        local.set 0
      end
      global.get 1
      i32.eqz
      if  ;; label = @2
        local.get 0
        return
      end
      unreachable
    end
    local.set 1
    global.get 2
    i32.load
    local.get 1
    i32.store
    global.get 2
    global.get 2
    i32.load
    i32.const 4
    i32.add
    i32.store
    global.get 2
    i32.load
    local.get 0
    i32.store
    global.get 2
    global.get 2
    i32.load
    i32.const 4
    i32.add
    i32.store
    i32.const 0)

I noticed this because a function that previously had 34 local variables now has 444 locals when compiled with the latest Emscripten (3.1.62).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions