-
-
Notifications
You must be signed in to change notification settings - Fork 670
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
Rework runtime #535
Rework runtime #535
Conversation
The more I work on this I feel like redoing the entire standard library from scratch would be a better approach. Already stumbled over 20 places where stuff wasn't properly linked with GC and whatnot. |
std/assembly/internal/sort.ts
Outdated
@@ -42,7 +33,7 @@ export function COMPARATOR<T>(): (a: T, b: T) => i32 { | |||
if (!alen && !blen) return 0; | |||
if (!alen) return -1; | |||
if (!blen) return 1; | |||
return compareUnsafe(<string>a, 0, <string>b, 0, <usize>min(alen, blen)); | |||
return String.cmp(<string>a, 0, <string>b, 0, <usize>min(alen, blen)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, wdyt add String.ord(a: string, b: string): i32
? But leave compareUnsafe
as is
Last commit integrates purerc and tlsf into one package (currently just named asrt for AssemblyScript Runtime), now with a single runtime header. In the process I actually stumbled upon a couple strange things in TLSF (for instance Also turns out that the common header (block overhead in TLSF) is exactly 16 bytes in WASM32, which is great. Doesn't hold true for WASM64, but considering that this isn't anywhere on the horizon this is fine with me. |
Still debugging TLSF. There's something very odd when picking fl/sl on allocations and determining maximum block size. Takes a while because I need to build a visual memory debugger for this to know for sure. |
I remember someone already built visual debugger and as I remember tlsf was very fragmented |
What I'm after specifically is something that shows fl/sl/heads visually, with a way to allocate and free by clicking, on top of a debug build of asrt that logs stuff :) |
Is there any way to query all the allocations? I have a few ideas. |
No, TLSF doesn't keep lists of "used" blocks, just free blocks. The way this works is that it picks a suitable block from free blocks, possibly splitting a larger one (leaving the remainder as a free block), and giving the allocated block to the user, forgetting about it. Once that's free'd again, it's inserted back into free lists and possibly merged with adjacent free blocks. I think the visualizer I'm working on will explain the concept quite well :) |
To try it out, run |
seems useful to keep the number of runtime functions in a user's binary to a minimum
Ok I think the allocator should be fairly robust now, but feel free to play with the visualizer and break it. Let me know if you somehow manage to :) Now thinking how to visualize RC. Maybe similar to the fuzz stuff on the purerc repo, with buttons to allocate objects and arrays of various sizes and dragging/dropping them or pushing retain/release buttons - but not sure if that's worth it and if I should instead wire stuff into the compiler right away. |
Idea now is that some sort of runtime must be present in every program in the future, be it our TLSF/PureRC runtime or something custom. Difference to before is that not the allocators and collectors are pluggable, but the runtime is pluggable and will be fixed to ARC (no tracing anymore because the cognitive and code overhead simply isn't worth it imo). It'll still be possible to have a stub runtime without a GC and just the arena allocator, of course, as long as it implements the RT interface. Some info on the proposed interface is here. |
As you might have guessed, rethinking/rewriting this stuff over and over again turned out to be somewhat exhausting on my end. Current state is that I'm looking at completely red code that came from removing the old memory allocators, garbage collectors and previous runtime efforts, while trying to wrap my head around how reference counting (here: automatic insertion of runtime calls like retain/release) can integrate with stdlib which sometimes needs to use these interfaces manually by means of |
Quick update: Code isn't red anymore, integrates with updated stdlib and uses the new runtime API, but still lacks some of the RC integration when it comes to locals. Essentially, whenever a reference is provided as a function argument, it must be retained pre-call and released post-call (possibly triggering free), plus I still have to investigate retain/release calls on locals when assigning references between them. From what I've learned so far, much of the work on Swift, which uses ARC, evolves around static elimination of retain/release calls where refcount can be statically proven to remain unchanged, because each increment and decrement may involve a cache miss. From my current perspective, getting something working out the door first has priority ofc. |
So I've come to the conclusion that whenever I try to be smart about avoiding unnecessary retain/release calls, this tends to result in situations that can't be solved without further code analysis down the road, which we don't do but usually ask Binaryen to do after transforming to SSA form and similar. Currently looks like the compiler itself must be super paranoid about everything reference counting at first and then rely on optimization passes anyway. Then doing it the paranoid way leads to the return value problem again, where since we don't have a stack there must be some mechanism to make sure that these don't become released before the caller possibly retains them. I think Obj-C calls this autorelease pools or something, need to investigate. |
Turns out I might actually be able to be smarter than expected about this, completely eliminating retains/releases of locals and avoiding the need for autorelease pools, by using special temp locals with an AUTORELEASE property. Chances are that I'm overlooking something again, of course, but looks promising: class Ref {}
// Returning a reference first retains it on return and then
// is tracked by the caller's surrounding scope:
function returnRef(): Ref {
return /* __retain( */ changetype<Ref>(0) /* ) */;
}
export function testReturnRef(): void {
/* TEMP = */ returnRef();
// __release(TEMP)
}
// Taking a reference doesn't insert anything. If there was an
// allocation, it would be handled by the caller.
function takeRef(ref: Ref): void {
}
export function testTakeRef(): void {
takeRef(changetype<Ref>(0));
}
// Allocating a reference keeps track of it in the surrounding
// scope and releases it on exit.
export function newRef(): void {
/* TEMP = */ new Ref();
// __release(TEMP)
}
// Assigning a reference to a global retains it, releasing the old value.
var glo: Ref;
export function assignGlobal(): void {
glo = /* __retainRelease( */ changetype<Ref>(0) /* , glo) */;
}
// Assigning a reference to a field retains it, releasing the old value.
class Target { fld: Ref; }
export function assignField(): void {
changetype<Target>(0).fld = /* __retainRelease( */ changetype<Ref>(0) /* , fld) */;
} Remains the question: What am I missing this time? :) Edit: Doesn't work, again. |
Closing in favor of #592 |
As mentioned a few times, I'm relatively unhappy with our internal runtime. More precisely, the current runtime stacked stuff on top of other stuff at random places in order to get things going, starting at memory allocation level, putting ArrayBuffers, Strings and other classes on top, introducing cumbersome macro helpers, and then there's GC.
So this PR aims at designing something better, a common runtime that can be used for all sorts of tasks, using a common header for all objects, providing common helpers for internal use, eliminating unnecessary helpers, simplifying buffer layout and so on.
See
std/assembly/runtime.ts
for the proposed implementation.Differences are:
HEADER
with their class id (forinstanceof
etc.) and their payload size (for easy realloc and length/byteLength computation).LOAD
andSTORE
macros are gone. Strings and ArrayBuffers don't have their own header anymore and start with their data immediately, making it possible to justload
andstore
from/to them. Actually one can, theoretically, even cast between a String and an ArrayBuffer now.xyzUnsafe
helpers become mostly redundant and most of them will be removed (still need to think about copyUnsafe etc.).reallocate
ed always anddiscard
ed as long as notregister
ed yet.register
ed or it will leak. Upside: One can work with scratch objects and throw them away if necessary.