The store via data is instrumented, so it knows that it’s overwriting something that overlaps with a pointer, and it knows to mark it as containing an integer rather than a memory address at the same time.
Fil-C does not do this. Fil-C never allows the type of memory to change. If you store an int to a ptr or a ptr to an int, you trap and die.
This also enables good redundant check elimination. The compiler can know (but currently doesn’t lmao) that the moment you do any access to a pointer, then after that access, the pointer can only point to memory for which that access would have succeeded.
Btw, did you consider trying to compress the pointer format by getting rid of the sidecar and using a floating-point format for the bounds? This would require rounding up allocation sizes to match the fp rounding—so e.g. char *x = malloc(big); x[big] might fail to trap—but that doesn’t seem like an awful tradeoff.
That’s what we do in CHERI. For allocation-bounded things it’s totally fine. The rounding that an allocator does of size classes is far coarser than we require. It can be annoying enforcing sub-object bounds, particularly if you want something like a slice type: we can’t represent an arbitrary range of a very large array. You won’t hit this unless you’re doing sub-object bounds for multi-MiB objects on a 64-bit system, but it can be annoying for things like separate capabilities for header and data in an encoded video frame.
My experience from building languages tells me that it’s super valuable to have the loosest possible coupling between compiler, runtime, and memory management during early bringup. Rounding up allocation sizes to whatever the pointer encoding handlings is a tight coupling.
So, it’s not that the compression you propose is bad, it’s just that it would slow down the pace of Fil-C development if I introduced it today.
If Fil-C becomes successful and folks start screaming for perf then this approach is one of the approaches I would consider using, provided that SideCap size was actually the longest pole in the tent.
If you store an int to a ptr or a ptr to an int, you trap and die.
We were talking about the case where you have a union of a pointer and an int—that obviously can’t trap and die, right? (That said, I don’t understand why you would trap and die anyway; what are ‘boxed integers’ for if not this sort of thing?)
GC write barrier
Lots of different designs, though you’re right they usually only target pointer stores; but they generally cost a little bit more than that. The main designs I know are: 1) card-mark: unconditional ‘card_table[pointee >> k] = 0’, 2) g1-style region check: ‘if ((pointer ^ pointee) >> k) goto slow’.
We were talking about the case where you have a union of a pointer and an int—that obviously can’t trap and die, right? (That said, I don’t understand why you would trap and die anyway; what are ‘boxed integers’ for if not this sort of thing?)
That traps and dies in Fil-C.
Lots of different designs, though you’re right they usually only target pointer stores; but they generally cost a little bit more than that. The main designs I know are: 1) card-mark: unconditional ‘card_table[pointee >> k] = 0’, 2) g1-style region check: ‘if ((pointer ^ pointee) >> k) goto slow’.
I’m a GC designer. Usually the good barriers are much cheaper than what you say. And they cost nothing (the overhead gets lots in the spare bandwidth of the CPU’s pipe).
One of the best barriers is the one that Java uses for its default gengc. That’s sort of the gold standard against which others are measured. It’s not hard to get there, lots of others have gotten there.
You will have to be more specific. The most popular java implementation is hotspot, and hotspot’s default gc is g1; g1’s write barrier is, as I said, if ((pointer ^ pointee) >> k) goto slow—remset maintenance iff pointer and pointee belong to different regions.
Fil-C does not do this. Fil-C never allows the type of memory to change. If you store an int to a ptr or a ptr to an int, you trap and die.
This also enables good redundant check elimination. The compiler can know (but currently doesn’t lmao) that the moment you do any access to a pointer, then after that access, the pointer can only point to memory for which that access would have succeeded.
score 3 · canonical
Btw, did you consider trying to compress the pointer format by getting rid of the sidecar and using a floating-point format for the bounds? This would require rounding up allocation sizes to match the fp rounding—so e.g. char *x = malloc(big); x[big] might fail to trap—but that doesn’t seem like an awful tradeoff.
score 1 · canonical
That’s what we do in CHERI. For allocation-bounded things it’s totally fine. The rounding that an allocator does of size classes is far coarser than we require. It can be annoying enforcing sub-object bounds, particularly if you want something like a slice type: we can’t represent an arbitrary range of a very large array. You won’t hit this unless you’re doing sub-object bounds for multi-MiB objects on a 64-bit system, but it can be annoying for things like separate capabilities for header and data in an encoded video frame.
score 2 · canonical
I’ve thought of that and it’s a good idea.
My experience from building languages tells me that it’s super valuable to have the loosest possible coupling between compiler, runtime, and memory management during early bringup. Rounding up allocation sizes to whatever the pointer encoding handlings is a tight coupling.
So, it’s not that the compression you propose is bad, it’s just that it would slow down the pace of Fil-C development if I introduced it today.
If Fil-C becomes successful and folks start screaming for perf then this approach is one of the approaches I would consider using, provided that SideCap size was actually the longest pole in the tent.
score 3 · canonical
We were talking about the case where you have a union of a pointer and an int—that obviously can’t trap and die, right? (That said, I don’t understand why you would trap and die anyway; what are ‘boxed integers’ for if not this sort of thing?)
Lots of different designs, though you’re right they usually only target pointer stores; but they generally cost a little bit more than that. The main designs I know are: 1) card-mark: unconditional ‘card_table[pointee >> k] = 0’, 2) g1-style region check: ‘if ((pointer ^ pointee) >> k) goto slow’.
score 1 · canonical
That traps and dies in Fil-C.
I’m a GC designer. Usually the good barriers are much cheaper than what you say. And they cost nothing (the overhead gets lots in the spare bandwidth of the CPU’s pipe).
score 3 · canonical
why?
What do you call a ‘good barrier’? G1 is a rather well-regarded gc and uses the second barrier type I indicated.
Costs nothing on the backend, sure. Very little code is backend-bottlenecked. But yes barriers are rather cheap in practice.
score 2 · canonical
One of the best barriers is the one that Java uses for its default gengc. That’s sort of the gold standard against which others are measured. It’s not hard to get there, lots of others have gotten there.
score 3 · canonical
You will have to be more specific. The most popular java implementation is hotspot, and hotspot’s default gc is g1; g1’s write barrier is, as I said, if ((pointer ^ pointee) >> k) goto slow—remset maintenance iff pointer and pointee belong to different regions.
score 4 · canonical