Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rust backend: mutable reference to mutable static is deprecated #1005

Open
bluenote10 opened this issue Mar 24, 2024 · 6 comments
Open

Rust backend: mutable reference to mutable static is deprecated #1005

bluenote10 opened this issue Mar 24, 2024 · 6 comments

Comments

@bluenote10
Copy link
Contributor

Starting with Rust version 1.77 (released a few days ago), there is a new compiler warning related to "mutable reference to mutable static".

Given a simple Faust DSP like

import("stdfaust.lib");

envelope(gate) = gate : en.adsr(0.01, 0.01, 0.3, 0.1);
osci(f) = (os.osc(f) * 0.5 + os.osc(f*2) * 0.25 + os.osc(f*3) * 0.125);
synth(gate, freq) = envelope(gate) * osci(freq);

process = synth;

The code generation will currently produce a pattern like this (omitting everything but the relevant stuff here):

static mut ftbl0GenOscillatorSIG0: [F32; 65536] = [0.0; 65536];

impl FaustDsp for GenOscillator {

    // ...

    fn class_init(sample_rate: i32) {
        let mut sig0: GenOscillatorSIG0 = newGenOscillatorSIG0();
        sig0.instance_initGenOscillatorSIG0(sample_rate);
        sig0.fillGenOscillatorSIG0(65536, unsafe { &mut ftbl0GenOscillatorSIG0 }); // <= problem here
    }

}

This produces the following warning:

warning: creating a mutable reference to mutable static is discouraged
   --> faust/src/gen_oscillator.rs:156:52
    |
156 |         sig0.fillGenOscillatorSIG0(65536, unsafe { &mut ftbl0GenOscillatorSIG0 });
    |                                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable reference to mutable static
    |
    = note: for more information, see issue #114447 <https://github.com/rust-lang/rust/issues/114447>
    = note: this will be a hard error in the 2024 edition
    = note: this mutable reference has lifetime `'static`, but if the static gets accessed (read or written) by any other means, or any other reference is created, then any further use of this mutable reference is Undefined Behavior
    = note: `#[warn(static_mut_refs)]` on by default
help: use `addr_of_mut!` instead to create a raw pointer
    |
156 |         sig0.fillGenOscillatorSIG0(65536, unsafe { addr_of_mut!(ftbl0GenOscillatorSIG0) });
    |                                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Note however, that this is only a warning for now. In the future it will become a hard error, so just silencing the warning is not an option. A bit more background can be found here:

I haven't looked into it in details, but on first glance this could indeed be a case of "insta-UB", when class_init is called concurrently from different threads.

Considering that this buffer is essentially a static lookup thingy, a thread_local! approach may be the most appropriate solution, but I haven't investigated yet how difficult it would be to incorporate it into the code generation.

@sletz
Copy link
Member

sletz commented Mar 24, 2024

OK... PR welcome !

@crop2000
Copy link
Contributor

crop2000 commented Dec 9, 2024

I think thread_local! would prevent the use of DSPs that make use of global buffers on multiple threads, i think this is not a good solution.
in this thread https://users.rust-lang.org/t/mutex-but-non-blocking-on-reads/95620
two crates are suggested: arc-swap and left-right they provide lock-free read access.

@bluenote10
Copy link
Contributor Author

I think thread_local! would prevent the use of DSPs that make use of global buffers on multiple threads

I'm not entirely sure about that. If the buffer initialization is deterministic, each thread could lazily run the initialization the first time the access comes via that thread. The DSP structs themselves may remain Send as far as I can see.

In general, having compute calls coming in from constantly changing threads is a bit of an odd use case anyway, right? So one could also argue that a !Send solution may be fine as well -- but I also don't really want to give up on it.

One should mention that the current implementation is likely the worst, because it claims to be Send but that invalid usage of unsafe probably means that multithreaded usage is rather a bug/UB, and we should have mark them as !Send/!Sync or so anyway.

@sletz
Copy link
Member

sletz commented Dec 9, 2024

A bit of history here: those global variables (and the associated "sub classes" model generated when rdtable/rwtable primitives are used) where added to share data between different DSP instances, basically to save memory for huge tables.

We later introduced the -it/-inline-table to have the alternative of "not sharing data anymore between instances". But -it/-inline-table is not yet implemented in Rust backend. We should maybe simply at this ?

@crop2000
Copy link
Contributor

crop2000 commented Dec 9, 2024

I have difficulty to imagine how thread_local would work with multiple readers on different threads.
The before mentioned libraries would facilitate this.
The -it is a possible solution for a cases where it is more important to safe the lock on the buffer and it is ok to have multiple buffers for multiple instances of the dsp.

would it be an option to have three variants?
lockfree
thread_local
inline-table

i am most interested in the lockfree variant.

@bluenote10
Copy link
Contributor Author

As far as I can see, thread_local would be lock-free and wait-free, and should be the most performant solution when used from a single thread -- the standard use case.

Everything going in the direction of arc-swap is nice-to-have because it actually shares the buffers across threads. But in terms of performance this can come at a certain cost, due to atomic ref counting and/or usage of lock-free, but not wait-free access patterns.

So I would see the three variants as:

  • shared (what you called lockfree, which is an implementation detail of how one actually implements the sharing).
  • thread_local (or shared_thread)
  • non-shared (or inline-table, exclusive, fully-isolated)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants