nim-lang / Nim

Nim is a statically typed compiled systems programming language. It combines successful concepts from mature languages like Python, Ada and Modula. Its design focuses on efficiency, expressiveness, and elegance (in that order of priority).
https://nim-lang.org
Other
16.44k stars 1.47k forks source link

Alignment fault when compiling with Emscripten #17026

Closed oakes closed 3 years ago

oakes commented 3 years ago

A particular use of hashed collections (Tables and HashSets), possibly in combination with object variants and generics, in code compiled with Emscripten leads to a mysterious "alignment fault". I'm using gc:arc (see here for all the options i'm setting).

I first noticed this when compiling a game that uses my library, pararules. I knew that would be an unhelpful bug report so i spent a lot of time finding a minimal case and this is as small as i could make it:

Example


import tables, sets

type
  Match[T] = Table[string, T]
  Matches[T] = Table[string, Match[T]]
  FactKind = enum
    FactKind0, FactKind1, FactKind2
  Fact = object
    case kind: FactKind
    of FactKind0:
      slot0: int
    of FactKind1:
      slot1: float
    of FactKind2:
      slot2: HashSet[int]

var matches: Matches[Fact]

proc setMatch(matches: var Matches, key: string, match: Match) =
  matches[key] = match

proc tick() =
  var match: Match[Fact]
  match["foo"] = Fact(kind: FactKind1, slot1: 0f)
  matches.setMatch("foo", match)
  echo matches

when defined(emscripten):
  proc emscripten_set_main_loop(f: proc() {.cdecl.}, a: cint, b: bool) {.importc.}

  proc mainLoop() {.cdecl.} =
    tick()

  emscripten_set_main_loop(mainLoop, 0, true)

Current Output

In a browser console you will see:

exception thrown: RuntimeError: abort(alignment fault) at Error

Expected Output

{"foo": {"foo": (kind: FactKind1, slot1: 0.0)}}

Possible Solution

$ nim -v
Nim Compiler Version 1.4.2 [MacOSX: amd64]
Compiled at 2020-12-21
Copyright (c) 2006-2020 by Andreas Rumpf

active boot switches: -d:release
yglukhov commented 3 years ago

Could it be a GC problem? You don't seem to take extra care of it. Does it reproduce with gc disabled?

oakes commented 3 years ago

With gc:none the problem does not reproduce. I am trying to use gc:arc (see my config.nims for all the options i have set). Not sure what you mean by taking extra care of it.

EDIT: I don't think the problem is limited to ARC. I tried building a larger game project and even gc:none seems to experience some kind of memory corruption related to my use of HashSet and Table.

yglukhov commented 3 years ago

Default nim gc uses imprecise stack scanning, which is not possible in asm.js/wasm since there's no "stack". This means the GC might erroneously delete living object, reallocate new stuff on top of them, leading to all kinds of memory corruptions. To fix it you have to structure your program in a way that accounts for that. The easiest way would be to disable gc early on start and call it close to the bottom of the stack (likely your runloop), where no nim references exist. Also arc/orc should theoretically work as it doesn't scan the stack.

EDIT: For the reference, here's how nimx solves it: https://github.com/yglukhov/nimx/blob/master/nimx/private/windows/emscripten_window.nim#L436-L449

oakes commented 3 years ago

Given that the error happens with arc is it safe to say this is a bug?

In the meantime I can try manually calling GC_step but i'm confused -- where is your code getting it from? I'm getting an undeclared identifier error. I tried importing system/gc but that gives me the same error, but for hasThreadSupport.

yglukhov commented 3 years ago

Given that the error happens with arc is it safe to say this is a bug?

Oh somehow I overlooked that. I'm not sure exactly about ARC internals, so can't say.

where is your code getting it from?

It is only availabe in realtime gc mode useRealtimeGC. Or at least used to be. Haven't been using emscripten for quite a while.

oakes commented 3 years ago

Yeah i tried nim c -d:emscripten -d:useRealtimeGC alignerror.nim and still get the undeclared identifier error for GC_step. Nonetheless i think i can work around the issue for the time being -- as long as i store collections in pararules as ref types like ref HashSet it seems to behave.

pyokagan commented 3 years ago

With gc:none the problem does not reproduce. I am trying to use gc:arc (see my config.nims for all the options i have set). Not sure what you mean by taking extra care of it.

I notice you are using --cpu:i386. Does it reproduce with --cpu:wasm32?

oakes commented 3 years ago

Nope, that is a great suggestion, I didn't know about wasm32. I'll keep trying to reproduce with wasm32 but if i can't i'll close the issue.

oakes commented 3 years ago

My dungeon crawler game is still experiencing some kind of memory corruption issue, even with --cpu:wasm32, but it is not specifically saying "alignment fault" so i'm gonna close this issue. If i can reduce the problem down again maybe i'll open a new issue later. Thanks @pyokagan