Programming thread

  • Want to keep track of this thread?
    Accounts can bookmark posts, watch threads for updates, and jump back to where you stopped reading.
    Create account
>have good project idea
>know what I want from it
>don't know where to start
How do you guys typically get over that initial hump of "what do I do first"? I have a bunch of pieces that I need to create, and I know a at a high level what I want, but I'm getting stuck on implementation details
pencil-writing.jpg
Write it down. Start with the highest level and write out what you know, and what you don't know. Break it down into pieces: I want to shove this data chunk from point A to point B, well I need a receiver at point B, and some kind of authentication, and a format that they both understand under the constraints... write all of this down.

This will give you flexibility and insight into how to build anything before you even touch a computer. Fill up a page or two with ways you don't want to build it and you will have a grasp on how you do. This is how you emerge the small details from the large ones because you'll have thought of the whole system at least in a cursory manner with diagrams, scribbles, and guesses. Soon you can cordon off a big chunk of your thoughts and turn it into concrete implementation plans, something that you can't do easily inside the text editor.

Also recommended: a nice pen that feels good in your hands, a folder or portfolio that you can store all of the pages in, and a coffee.

My problem is that I'm so anal about my own DX that I try to make every internal API function work well, and I end up frontloading a lot of work before even getting the problem solved
Get familiar with the /* todo: some shit that needs to be here */ block. Do the important straight-line path first and edge cases second after you have the rough clay to start and confidence that after a few tests you're going in the right direction.
 
Also recommended: a nice pen that feels good in your hands, a folder or portfolio that you can store all of the pages in, and a coffee.
I actually have a chalkboard, I'm just used to mostly writing code directly rather than pre-planning. I've started doing org docs for writing shit down, but I have a tendency to get off topic and they read more like manifestos.
 
My problem is that I'm so anal about my own DX that I try to make every internal API function work well, and I end up frontloading a lot of work before even getting the problem solved
This can be changed at any point, and it almost certainly will change over the course of development. There is no perfect API, just as there is no perfect sorting algorithm. The only way to find the best API for your project is to build and refine as you go. One of the great things about modern version control software is that you can just make major changes and if anything breaks you will know exactly what you've changed and where, and you can easily revert to a known good state.

If you plan things out too much (and too rigidly) before gaining the required experience, you'll either end up planning yourself into a corner and giving up, or forcing yourself to complete a poor design that you don't like.

Looking at some of my projects from even just a few years ago, there are some questionable design choices that probably made sense to me at the time... Even if you were to achieve perfection today, who knows how you'll feel about things tomorrow?
 
the most basic bitch implementation
My approach as well. Build as minimal as you can get away with; you can add features later. If that smallest minimal retarded core will take more than a session, you'll need to break it into units, and if it's that complicated, build tests, because I start losing easy fluency in these contexts and can mess up details as I iterate.
 
You don't paint a wall all at once, there are lots of ways to do it but everyone starts at a single point and works out from there.
If you don't know where to put that point there is no harm in picking the easiest option.
 
This can be changed at any point, and it almost certainly will change over the course of development. There is no perfect API, just as there is no perfect sorting algorithm. The only way to find the best API for your project is to build and refine as you go. One of the great things about modern version control software is that you can just make major changes and if anything breaks you will know exactly what you've changed and where, and you can easily revert to a known good state.
I use pretty basic version control with Git but it's worth saying to @Anti Snigger that if you think you are about to make big changes and reverting them would be a pain even with version control, start a branch, which will prevent any risk of deterioration to master (or whatever you use, but in my case I make it clear that I whip uppity niggers around here). Merging test feature branches with the primary branch is usually not too hard and tools like Lazygit or Magit will make it easier.
 
Welcome to Fil-C, a memory safe implementation of the C and C++ programming languages you already know and love.

What is Fil-C?
Fil-C is a fanatically compatible memory-safe implementation of C and C++. Lots of software compiles and runs with Fil-C with zero or minimal changes. All memory safety errors are caught as Fil-C panics. Fil-C achieves this using a combination of concurrent garbage collection and invisible capabilities (InvisiCaps). Every possibly-unsafe C and C++ operation is checked. Fil-C has no unsafe statement and only limited FFI to unsafe code.


Just discovered this. Any of y'all wanna opine? Rustroons malding, coping, seething, and dilating?
 
Welcome to Fil-C, a memory safe implementation of the C and C++ programming languages you already know and love.

What is Fil-C?
Fil-C is a fanatically compatible memory-safe implementation of C and C++. Lots of software compiles and runs with Fil-C with zero or minimal changes. All memory safety errors are caught as Fil-C panics. Fil-C achieves this using a combination of concurrent garbage collection and invisible capabilities (InvisiCaps). Every possibly-unsafe C and C++ operation is checked. Fil-C has no unsafe statement and only limited FFI to unsafe code.


Just discovered this. Any of y'all wanna opine? Rustroons malding, coping, seething, and dilating?
Is this functionally different than "safe C" or any of the other C+memory safety options? Or is this just xkcd 927?
 
Welcome to Fil-C, a memory safe implementation of the C and C++ programming languages you already know and love.

What is Fil-C?
Fil-C is a fanatically compatible memory-safe implementation of C and C++. Lots of software compiles and runs with Fil-C with zero or minimal changes. All memory safety errors are caught as Fil-C panics. Fil-C achieves this using a combination of concurrent garbage collection and invisible capabilities (InvisiCaps). Every possibly-unsafe C and C++ operation is checked. Fil-C has no unsafe statement and only limited FFI to unsafe code.


Just discovered this. Any of y'all wanna opine? Rustroons malding, coping, seething, and dilating?
Isn't that just fancy address sanitizer? You still pay runtime cost.
 
https://fil-c.org/invisicaps -- this explains how its memory safety approach works; apparently each pointer gets tagged with information saying what it's allowed to do, and the compiler ensures that there is no breach of contract

Edit: Elsewhere, from https://fil-c.org/fugc

---

Fil-C uses a parallel concurrent on-the-fly grey-stack Dijkstra accurate non-moving garbage collector called FUGC (Fil's Unbelievable Garbage Collector). You can find the source code for the collector itself in fugc.c, though be warned, that code cannot possibly work without lots of support logic in the rest of the runtime and in the compiler.
Let's break down FUGC's features:
  • Parallel: marking and sweeping happen in multiple threads, in parallel. The more cores you have, the faster the collector finishes.
  • Concurrent: marking and sweeping happen on some threads other than the mutator threads (i.e. your program's threads). Mutator threads don't have to stop and wait for the collector. The interaction between the collector thread and mutator threads is mostly non-blocking (locking is only used on allocation slow paths).
  • On-the-fly: there is no global stop-the-world, but instead we use "soft handshakes" (aka "ragged safepoints"). This means that the GC may ask threads to do some work (like scan stack), but threads do this asynchronously, on their own time, without waiting for the collector or other threads. The only "pause" threads experience is the callback executed in response to the soft handshake, which does work bounded by that thread's stack height. That "pause" is usually shorter than the slowest path you might take through a typical malloc implementation.
  • Grey-stack: the collector assumes it must rescan thread stacks to fixpoint. That is, GC starts with a soft handshake to scan stack, and then marks in a loop. If this loop runs out of work, then FUGC does another soft handshake. If that reveals more objects, then concurrent marking resumes. This prevents us from having a load barrier (no instrumentation runs when loading a pointer from the heap into a local variable). Only a store barrier is necessary, and that barrier is very simple. This fixpoint converges super quickly because all newly allocated objects during GC are pre-marked.
  • Dijkstra: storing a pointer field in an object that's in the heap or in a global variable while FUGC is in its marking phase causes the newly pointed-to object to get marked. This is called a Dijkstra barrier and it is a kind of store barrier. Due to the grey stack, there is no load barrier like in the classic Dijkstra collector. The FUGC store barrier uses a compare-and-swap with relaxed memory ordering on the slowest path (if the GC is running and the object being stored was not already marked).
  • Accurate: the GC accurately (aka precisely, aka exactly) finds all pointers to objects, nothing more, nothing less. llvm::FilPizlonator ensures that the runtime always knows where the root pointers are on the stack and in globals. The Fil-C runtime has a clever API and Ruby code generator for tracking pointers in low-level code that interacts with pizlonated code. All objects know where their outgoing pointers are - they can only be in the InvisiCap auxiliary allocation.
  • Non-moving: the GC doesn't move objects. This makes concurrency easy to implement and avoids a lot of synchronization between mutator and collector. However, FUGC will "move" pointers to free objects (it will repoint the capability pointer to the free singleton so it doesn't have to mark the freed allocation).
This makes FUGC an advancing wavefront garbage collector. Advancing wavefront means that the mutator cannot create new work for the collector by modifying the heap. Once an object is marked, it'll stay marked for that GC cycle. It's also an incremental update collector, since some objects that would have been live at the start of GC might get freed if they become free during the collection cycle.
FUGC relies on safepoints, which comprise:
  • Pollchecks emitted by the compiler. The llvm::FilPizlonator compiler pass emits pollchecks often enough that only a bounded amount of progress is possible before a pollcheck happens. The fast path of a pollcheck is just a load-and-branch. The slow path runs a pollcheck callback, which does work for FUGC.
  • Soft handshakes, which request that a pollcheck callback is run on all threads and then waits for this to happen.
  • Enter/exit functionality. This is for allowing threads to block in syscalls or long-running runtime functions without executing pollchecks. Threads that are in the exited state will have pollcheck callbacks executed by the collector itself (when it does the soft handshake). The only way for a Fil-C program to block is either by looping while entered (which means executing a pollcheck at least once per loop iteration, often more) or by calling into the runtime and then exiting.
Safepointing is essential for supporting threading (Fil-C supports pthreads just fine) while avoiding a large class of race conditions. For example, safepointing means that it's safe to load a pointer from the heap and then use it; the GC cannot possibly delete that memory until the next pollcheck or exit. So, the compiler and runtime just have to ensure that the pointer becomes tracked for stack scanning at some point between when it's loaded and when the next pollcheck/exit happens, and only if the pointer is still live at that point.
The safepointing functionality also supports stop-the-world, which is currently used to implement fork(2) and for debugging FUGC (if you set the FUGC_STW environment variable to 1 then the collector will stop the world and this is useful for triaging GC bugs; if the bug reproduces in STW then it means it's not due to issues with the store barrier). The safepoint infrastructure also allows safe signal delivery; Fil-C makes it possible to use signal handling in a practical way. Safepointing is a common feature of virtual machines that support multiple threads and accurate garbage collection, though usually, they are only used to stop the world rather than to request asynchronous activity from all threads. See here for a write-up about how OpenJDK does it. The Fil-C implementation is in filc_runtime.c.
Here's the basic flow of the FUGC collector loop:
  1. Wait for the GC trigger.
  2. Turn on the store barrier, then soft handshake with a no-op callback.
  3. Turn on black allocation (new objects get allocated marked), then soft handshake with a callback that resets thread-local caches.
  4. Mark global roots.
  5. Soft handshake with a callback that requests stack scan and another reset of thread-local caches. If all collector mark stacks are empty after this, go to step 7.
  6. Tracing: for each object in the mark stack, mark its outgoing references (which may grow the mark stack). Do this until the mark stack is empty. Then go to step 5.
  7. Turn off the store barrier and prepare for sweeping, then soft handshake to reset thread-local caches again.
  8. Perform the sweep. During the sweep, objects are allocated black if they happen to be allocated out of not-yet-swept pages, or white if they are allocated out of already-swept pages.
  9. Victory! Go back to step 1.
If you're familiar with the literature, FUGC is sort of like the DLG (Doligez-Leroy-Gonthier) collector (published in two papers because they had a serious bug in the first one), except it uses the Dijkstra barrier and a grey stack, which simplifies everything but isn't as academically pure (FUGC fixpoints, theirs doesn't). I first came up with the grey-stack Dijkstra approach when working on Fiji VM's CMR and Schism garbage collectors. The main advantage of FUGC over DLG is that it has a simpler (cheaper) store barrier and it's a slightly more intuitive algorithm. While the fixpoint seems like a disadvantage, in practice it converges after a few iterations.
Additionally, FUGC relies on a sweeping algorithm based on bitvector SIMD. This makes sweeping insanely fast compared to marking. This is made thanks to the Verse heap config that I added to libpas. FUGC typically spends <5% of its time sweeping.

 
Last edited:
>have good project idea
>know what I want from it
>don't know where to start
How do you guys typically get over that initial hump of "what do I do first"? I have a bunch of pieces that I need to create, and I know a at a high level what I want, but I'm getting stuck on implementation details
Depending on the context, I'll often mock out the cli interface I want to be able to use for it.

Sometimes the final goal I'm imagining is too complex, so I'll break it down into smaller cli commands that simulate just a call and response portion of the larger problem.

I frame out the program, including cli flag access to all the various parameters I can possibly think of that will be necessary.

For basically every project I do in basically every language I work in, I keep a little scrap text file of cli commands and repl snippets, where I can quickly open a few terminals in the project directory and start running code. And it'll error out at any portions I haven't implemented yet.

That gives me a place to hop into a "edit, save, (compile) and test" loop.

Not many of my projects end up as cli tools, but this is how I develop all of them to begin with regardless. It's a very "tactile" development process to me.

So for example, if I'm working on a service that will just end up responding to requests over grpc (myservice-cli run), I usually end up framing out myservice-cli handle-request and myservice-cli send-request. I frame out all the necessary grpc framework code and makefiles and get everything working to the point where I can run the handle-request command, and when I run the send-request command, it errors out.

That gives me a place to start and iterate from.

Like the current project I'm working on, I just finished framing the send-request side out and all the scaffolding leads around to this function:

Code:
let execute _settings =
  failwith "todo finish this"

Like having something tangible to run, even when it errors out, is a massive motivator for me compared to going from a blank slate.
 
I want to try Neovim, how fucked am I?
i personally use neovim not because some gay bullshit like "blazingly fast" or "very configurable" but because i dont want to move my hand between my mouse and my keyboard so often

and neovim does that task really really well

the downside is that any other text editor is now cumbersome to use
 
I don't get the appeal of vim and co tbh, emacs works great for me
 
I want to try Neovim, how fucked am I?
I don't get the appeal of vim and co tbh, emacs works great for me
LSP more or less just werks with Neovim. I intend to continue to Emacs for certain purposes like Magit and OCaml in the future but will use Neovim for everything else.
from what i know emacs doesnt have a concept of CWD and vim does
i personally prefer CWD existing
How does it not have CWD?
 
Back
Top Bottom