c3lang / c3docs-old

Docs for the C3 language
http://www.c3-lang.org
10 stars 15 forks source link

Proposal: Context #12

Closed lerno closed 2 years ago

lerno commented 5 years ago

Odin and Jai both use implicit contexts in their function calls. There are a few different uses, but the main appears to be to set a different allocator.

Here is an example taken from the Odin docs:

main :: proc() {
    c := context; // copy the current scope's context

    context.user_index = 456;
    {
        context.allocator = my_custom_allocator();
        context.user_index = 123;
        supertramp(); // the `context` for this scope is implicitly passed to `supertramp`
    }

    // `context` value is local to the scope it is in
    assert(context.user_index == 456);
}

supertramp :: proc() {
    c := context; // this `context` is the same as the parent procedure that it was called from
    // From this example, context.user_index == 123
    // An context.allocator is assigned to the return value of `my_custom_allocator()`

    // The memory management procedure use the `context.allocator` by default unless explicitly specified otherwise
    ptr := new(int);
    free(ptr);
}

To translate this to a C3 syntax, it would look like this:

func void main()
{
  context.user_index = 456;
  @new_context()
  {
    context.allocator = my_custom_allocator();
    context.user_index = 123;
    supertramp();
  }
  assert(context.user_index == 456);
}

void supertramp()
{
  assert(context.user_index == 123);
  int* ptr = @malloc(int);
  free(ptr);
}
lerno commented 5 years ago

Methods of implementation:

  1. Context is pointed to by a thread local variable.
  2. Context is a hidden parameter.

Thread local variable

Pros

Zero overhead unless used. Can be used seamlessly with C without altering calling conventions.

Cons

Requires a stack of contexts. This can be done by statically pre-allocating an array of contexts per thread. Push/pop of contexts need to be ensured.

Hidden parameter

Pros

No need to ensure push/pop. Extremely simple and straightforward implementation. Variable context size is simple to accommodate. All can be allocated on the stack. Easier to check for purity. No need to access thread local storage.

Cons

The parameter is not ABI friendly to C, affecting callbacks sent to C and also requiring more awareness of the boundaries between C and C3. Although transparent and easy to understand, the "magic variable" is introducing a variable with very special implicit behaviour.

lerno commented 5 years ago

Possible uses:

  1. Custom allocator without the need to explicitly pass the allocator
  2. Storage of thread local variables, including thread id.
  3. Storage of optional error information, for example stack trace.
  4. Logging
  5. Arbitrary stacked data (similar to 2)
  6. Discriminator side channel for errors
PavelVozenilek commented 4 years ago

While implicit context sounds as useful, there are following problems:

  1. Third party libraries don't know what will/won't be available in the context. The language may demand minimal content of implicit context, but there's only little (allocator, stdin/stderr like logger). Then it all looks as much ado about nothing.

  2. Tests would require creating their own context, even when it is not used. Lot of busywork.

Why not have two hidden global (per thread) variables in the program? One for the default allocator, one for the default logger. It would be filled by something reasonable by the compiler (for main and for every new thread and for every test), it could be overwritten explicitly at any place, if desired.

Application specific global context could then be implemented by an ordinary global variable. Main/threads/tests would need to fill it up explicitly.

The language may have restriction allowing only one global variable. That would be an useful constraint also acting as the implicit context.

lerno commented 4 years ago
  1. That is a valid criticism, however - it might be that these this minimal content is sufficiently important to have as a context.
  2. Why?

What you want is the stack behaviour. One advantage of the context passed around is that it is also safe for use with fibers.

PavelVozenilek commented 4 years ago

If tests are implemented as isolated as much as possible, then each may require its own context. Sharing one big app context everywhere, including stress testing tests, is not wise.

Now thinking about it, what if a library has its own tests? How would these work with other libraries and with the application?

Fibers are not hot feature everyone wishes to have, whay to add them into the language?

lerno commented 4 years ago

Each text can set up the context entirely to its own liking so I don't understand the question.

Re: fibers, it's just an observation that the thread local solution prevents contexts from being used correctly in this case.

lerno commented 2 years ago

Closing this. Currently C3's stdlib relies on thread locals.