#shkwve: an incomplete kernel/maybe bootloader/UEFI thing/maybe something else if i get bored again

1 messages ยท Page 5 of 1

untold basin
#

okay i have bar alloc

#

time for irq routing

untold basin
#

okay im setting up virtio-blk

#

time to set up virtqueues

untold basin
#

hmm does virtio execute operations in order?

trim seal
#

i dont think it guarantees ordering ๐Ÿค”

untold basin
#

ah

#

thats fine i think

untold basin
#

okay i have a semi working virtio driver!

#

time to make it async (๐Ÿš€๐Ÿš€๐Ÿš€)

#

okay lets figure out interrupts then ig

untold basin
#

okay now i have a queue dpc

#

so hmm

#

should i write a shitty driver which submits at most one operation to the device

#

or should i write a good driver which can pipeline them

#

yeah no ill just put a lock around each virtqueue

#

i probably will have pipelined ops for xhci though

rose sonnet
#

a good driver is not that much work if you use coroutines meme

zenith lion
#

static PITUST: u128; is no more nooo

#

or const

untold basin
#

:^)

untold basin
#

but i wrote a bad driver that uses coroutines

#

and it works just fine

untold basin
untold basin
#

okay now i have handling for multiple virtqueues maybe

#

okay now i have a horrible virtio-console """driver"""

#

okay lets abstract it all out

untold basin
#

virtio-console, with a semi not shitty driver!

#

async, too

#

so now i guess i could do blk

#

or i could do net

#

okay, now i have a network device i think

untold basin
#

okay thats not quite right

#

(0xcc is what i fill new memory allocations with)

zenith lion
untold basin
#

okay now i have my shitty buffer wrapper actually working

untold basin
#

hmm, im not sure how to design my network stack

trim seal
#

In a bootloader?

untold basin
#

yes

#

im writing a good bootloader!

#

and that includes pxe boot

vapid echo
#

are you gonna do qr code booting

untold basin
vapid echo
#

ive already figured out how to do it

trim seal
vapid echo
#

i had code for limine

untold basin
#

"scan this qr code and upload binary to boot"

untold basin
#

and the uefi network stack is shit

trim seal
#

โ˜ ๏ธ

open osprey
untold basin
#

the other thing is i only care about support for like 3 protocols total

#

udp, tcp, icmpv4/v6

untold basin
#

okay i came up with a new way to do my network stack

open osprey
untold basin
#
        net::Packet p = net::build([&] <net::BuildPhase phase> (net::PacketBuilder<phase>& b) {
            // virtio
            b.u8(0); // flags
            b.u8(0); // gso type
            b.le16(0); // hedaer length
            b.le16(0); // gso size
            b.le16(0); // checksum start
            b.le16(0); // checksum offset

            // ethernet
            b.ether(net::EthAddr::broadcast);
            b.ether(ether);
            b.be16(cast<u16>(net::EtherType::IP));

            // ip
            IP ip{net::IPProto::UDP, net::IPv4Addr{255, 255, 255, 255}, net::IPv4Addr{0, 0, 0, 0}};
            ip.write(b);

            // udp
            usize len = b.payladSize();
            b.be16(68); // sport
            b.be16(67); // dport
            b.be16(len); // len
            b.be16(0); // checksum

            // test payload
            b.cstring("hello, world!"_s);
        });
#

something like this

#

and then i'll have higher-level interfaces for the higher-level protocols (udp, tcp, icmp), i think

#

then i do two passes, first to get the length

#

and then to write all the fields

trim seal
#

truly revolutionary

open osprey
untold basin
#

the problem is calculating checksums

untold basin
#

okay wtf somehow i have 160k of code

#

which is weird

#

hmm okay i think a lot of that is stack

#

its okay i think

#

thats my fdt::Node::is_compatible function

#

okay so some of it was -ffreestanding

#

now i have incompatible definitions of mem*

#

oh, oh no

#

my iterator wrappers have very bad codegen it seems

#

okay ill redo it then

#

to work less like rust and more like something approximating good codegen

untold basin
#

i wonder how much code size i'd save if i compiled out all of the dchecks

#

like 4k

#

hmm i think i might be failing even before virtio-net

#

compiling out dchecks makes it crash earlier

#

hmm maybe i should do ubsan

#

okay i am hitting ubsan

#

trap: type mismatch

#

lmao what

#

also thats a wrong decode i think

#

so the first one is a function type check

#

wtf

#

specifically in the event claim function

#

oh fun!

#

llvm bug :^)

#

im just adding more and more checks and hardening at this point lol

#

i have SSP too now

#

doesn't trip

#

same global corruption bug

#

if this is another llvm bug istg

#

i think my allocator is fucked

sacred spire
#

Duality of man

untold basin
#

now i'm getting PointerOverflow ubsan traps

#

it's from a vector push?

#

oh its a copy to null

#

shit

#

ah

#

zero length memcpies are broken

#

okay more memory corruption

#

okay now i hit a FunctionTypeMismatch

#

sounds like more heap corruption tbh

#

okay i added a vtable check

#

(inserted a magic cookie inside of the vtable and started checking it before doing calls)

#

maybe my promise type is broken?

#
    vf->vmspace.ref()._authenticateVtable();
    Promise<> _detached = [tp = vf->transport, vma = vf->vmspace] () -> Promise<> {
        vma.ref()._authenticateVtable();
        DMAMemory mem = vma->allocDMAMemory(4096).unwrap();
        while (true) {
            co_await tp->submit(0, {virtio::Element{mem.physAddr, 4096, virtio::Dir::DeviceToHost}});

            parsePkt(Span{cast<const u8*>(mem.virtAddr), 4096});
        }
    }();
``` the second call to _authenticateVtable dchecks
#

yeah okay i can repro under addresssanitizer

#

stack uaf

#

okay i think i dont get c++ coroutines, part N+1

#

fucking hell

#

reads my own code again

#

what the actual fuck did i write

#

and why

#

oh, OH

#

what the actual fuck are these semantics lmao

#

wonderful i think i have to rewrite my promise type

#

and my blocking run

#

and add a detached run function too i think

untold basin
#

okay i think im gonna try reading the old new thing and see if that helps me understand this crap

rose sonnet
untold basin
#

is that how it's meant to work?

#

also i think now my allocator is broken lmao

untold basin
#

ugh i think i might have more memory corruption still

#

oh this time its in-place OOB?

#

im somewhat tempted to port KASAN

untold basin
#

ah the magic option i really want is -mllvm -asan-mapping-offset

#

okay now i'm FARing 0x1fffa00008011704

#

okay now i have a sane asan base

#

still need to map it

#

ehh it sounds annoying

#

for fucks sake

#

i think i have uaf maybe?

#

okay i think my changes fixed the allocator

#

hopefully

#

maybe

rose sonnet
#

the latter decides who frees the coroutine afaik

untold basin
#

wtf

rose sonnet
untold basin
#

i have a mildly cursed idea

#

what if i added go code to shkwve

#

okay maybe i wont go that cursed

glad bough
rose sonnet
#

why not ultrameme

untold basin
#

no i dont think i will that sounds a bit too cursed even for me

rose sonnet
#

I mean I was planning to use it in pmOS for example, though the bad compiler kinda spoiled it

#

(though not in kernel)

untold basin
#

ah okay

untold basin
#

okay

#

im going back to shkwve

#

so far my progress is this

#

mapped into virtual memory in higher half

#

and full W^X

#

(there is a cpu bit to force enable w^x)

#

also i think my allocator is a bit fucked lol

vapid echo
#

What do the log signs mean

#

Like ~

trim seal
#

probably info/warn/error

vapid echo
#

ah I guess ~ is debug now that i think about it

untold basin
untold basin
#

my nvme is now nvmeing

#

and i need to think of a good way of implementing disk caches

jade dirge
#

Bootloader page cache meme

untold basin
#

of course!

#

i mean i already have a dma buffer allocator

#

and virtual memory

#

and a complement of synchronization primitives that would be acceptable for many full OSes

#

including something kinda like turnstiles ish

#

but more like parking_lot and go semaphores

#

including an entirely original and extremely unique API: ```rs
pub unsafe fn park(addr: usize) -> UnparkToken;
pub unsafe fn park_until(
addr: usize,
deadline: Instant,
) -> Result<UnparkToken, code!(Status::TIMEOUT)>;
pub unsafe fn unpark_one(
addr: usize,
callback: impl FnOnce(UnparkOneResult) -> UnparkToken,
) -> UnparkOneResult;
#[derive(Debug, Clone, Copy)]
pub struct UnparkOneResult {
pub unparked: bool,
pub be_fair: bool,
pub more_in_queue: bool,
}

#

and i have a one byte mutex based on that hmm, where have i heard of that idea before

untold basin
#

and none of my sync primitives allocate, obviously

#

well ok thats not quite true

#

i use a third party queue library for my scheduling queue which does actually allocate

#

but i still have an allocation-free timer heap

untold basin
vapid echo
#

Wtf

#

Honestly just turn this into a kernel

untold basin
#

yea i mean its pretty much a bad kernel at this point

#

except it also has a funky self relocating bootloader combined into its image

#

and i want to make it work as a real bootloader at some point

vapid echo
#

I should read about parking lot stuff

#

Is it another kind of lock?

untold basin
#

no

vapid echo
#

Or a lock backend

untold basin
vapid echo
#

Alright thanks

untold basin
#

its mostly a userspace futex

trim seal
untold basin
#

but thats boooring

#

plus it implies i have to care about things like actually having a userspace at some point

#

and it means i cant justify writing code with zero concern for OOM conditions under the guise of "yeah its fine its just a bootloader anyway"

trim seal
#

Lol

untold basin
#

also i have some cool ideas that dont make sense in a kernel

#

like for example, i had this idea to make an AML virtual machine which can be serialized back into functioning AML

#

unless i just say that my kernel has kexec support ๐Ÿ™ƒ

untold basin
#

it would not be EASY

#

but it would be COOL

#

and i dont think it would actually be THAT hard

trim seal
#

Like an AML interpreter in aml?

untold basin
#

no like

#

i write an AML interpreter which can turn its entire state into aml

trim seal
#

Hm

untold basin
#

okay im resurrecting this

#

shkwve is backkkkkkk kinda

#

shkwve: an incomplete kernel/maybe bootloader/UEFI thing/maybe something else if i get bored again

tawdry surge
#

is there a new repo?

untold basin
#

no

#

i didnt do the whole version control thing yet

#

and i need to unhardcode some paths first

#

right now i have this hardcoded lmao

#

but i also have epic stacktraces (only for kasan errors, i didnt wire it up for panics lmao)

#

(this is a random crash i inserted on purpose to show how cool my stacktraces are)

oh yeah also i got usermode code executing

trim seal
#

bro has usermode in a bootloader

untold basin
#

no its not a bootloader anymore

#

im planning to maybe do kexec at some point but im going full userspace now

#

im gonna try microkerneling

trim seal
#

lol

#

i like the direction change

untold basin
#

the name "shkwve" is really just "whatever project im working on right now" ngl

untold basin
#

and microsoft has that on the xbox one

#

well actually idk if its a thing in the xbox one bootloader, i know its a thing in the xbox one bootrom

trim seal
#

well the apple thing literally looks like macos

#

which it is probably?

untold basin
#

no like

trim seal
#

like when u pick a boot drive

untold basin
#

apple uefi literally does usermode

trim seal
#

it looks like mac os ui

untold basin
trim seal
untold basin
untold basin
#

option roms run in usermode iirc

vapid echo
#

Nice

untold basin
trim seal
#

doesnt uefi require all memory identity mapped

vapid echo
#

Is it proper compiler KASAN or just hardening features

untold basin
vapid echo
#

I might try implementing it

#

Didn't know rust had it

untold basin
untold basin
#

its very janky

tawdry surge
#

why is it janky?

vapid echo
#

isn't rust supposed to be safe

trim seal
#

no need for one since its memory safe trl

tawdry surge
#

isn't it just done by generic llvm passes

tawdry surge
#

;D

vapid echo
untold basin
trim seal
#

ur supposed to add a safety comment which makes it safe

untold basin
#
#[cfg_attr(feature = "kasan", inline(never))]
#[cfg_attr(feature = "kasan", sanitize(address = "off"))]
pub unsafe fn write_noasan<T>(ptr: *mut T, value: T) {
    if cfg!(feature = "kasan") {
        let src = ManuallyDrop::new(value);
        unsafe { memcpy(ptr as *mut (), &raw const src as *const (), size_of::<T>()) };
    } else {
        unsafe { *ptr = value };
    }
}
untold basin
#

i forgot

#

now its good

untold basin
#

but like a bunch of the pointer operations end up calling helpers

#

which ends up breaking

vapid echo
#

huh zig doesn't even have KASAN apparently

untold basin
#

because the helpers dont have kasan disabled

untold basin
tawdry surge
#

it's weird that you need inline(never), on Managarm we compile with LTO and we never ran into an issue where nosanitize was not respected

vapid echo
untold basin
#

a rust pointer load lowers to a call to a helper function which does some checks

tawdry surge
#

ah i see

untold basin
#

that function is in libcore, and has sanitizers forced on

untold basin
#

its normal addresssanitizer

vapid echo
#

ah

untold basin
#

built with this very simple set of flags

vapid echo
#

I know there's bespoke KASAN too

tawdry surge
#

but that's essentially KASAN

untold basin
tawdry surge
#

the difference to userspace ASAN is that the mapping offset is known a priori

trim seal
tawdry surge
#

other than that, I don't think there is a difference

untold basin
#

well

tawdry surge
#

it halves the size of the shadow mapping compared to the default

untold basin
#

yeah

#

it means you need half the amount of kasan shadow

trim seal
#

so the checks are more coarse?

untold basin
#

ish

#

in practice no

#

with kasan enabled, i put 16 bytes between each zone (~normal heap) allocation anyway

#

and the compiler does the same thing for stack stuff

untold basin
vapid echo
#

I only do poisoning when enabled and I think it's probably more than enough lol

untold basin
#

i dont think any of the -asan-opt-* flags do anything useful

#

my kasan runtime is quite complicated in general too

because it can print those awesome errors with inline-aware stacktraces

#

also its c++, its like the one(ish) piece of c++ in my kernel binary

vapid echo
#

Is that mostly dwarf

untold basin
#

no

#

i have a custom format

#

thats easier to parse

#

which saves probably quite a lot over dwarf too

vapid echo
#

How many languages do you have

#

Like 3?

#

I've never seen a pitust project not be a cursed mix of languages and I like it tbh

untold basin
#

3 + assembly + a tiny amount of shell script

trim seal
untold basin
untold basin
#

i make rustc emit dwarf

#

and then i have an insane dwarf parser and converter in my build tooling

trim seal
#

holy

tawdry surge
#

actually a good solution

untold basin
#

yes

tawdry surge
#

that's how i'd also do it

untold basin
#

if you dont want a binary thats 93% dwarf its basically the only viable approach i think

tawdry surge
#

dwarf is pretty bad, especially if you want to use it on a hotpath (such as profiling)

trim seal
#

have u checked how linux produces its orc stuff?

vapid echo
#

And then you strip the binary?

#

what is orc?

untold basin
#

no

trim seal
untold basin
vapid echo
trim seal
#

they never did

#

they did frame pointer unwinding then added this thing

untold basin
#

i mean for unwinding i just use frame pointers, i might do something like orc later

trim seal
#

theres also a guesser unwinder on x86

untold basin
#

im guessing its just an interval list of frame sizes?

vapid echo
#

I'm probably gonna do dwarf because zig has a parser

trim seal
untold basin
#

including inlines

trim seal
#

ohhh

untold basin
#

which is dwarf too

trim seal
#

for symbolizing i just use kallsyms from linux

untold basin
#

rust demangling is not fun

vapid echo
#

You don't mangle :)

trim seal
#

isnt there a function which allows you to demangle

#

without allocations

#

at runtime

untold basin
#

yeah in a crate

#

in a rust crate

trim seal
#

is that bad?

untold basin
#

so id have to ship a demangler

#

well i wrote this when i wanted to do kasan

#

and the kasan runtime is very intentionally not written in rust

trim seal
#

why?

untold basin
# trim seal why?

you dont want to be using kasan-instrumented code anywhere within the kasan instrumentation impl

trim seal
#

cant u just mark it as non instrumented

untold basin
#

no

#

i mean yes i can mark my thing as not instrumented

#

but all of the things it calls will still get instrumented as normal

untold basin
#

and you cant really mix two different rust static libraries and have them work, plus thats really bad for code size

#

the crate also doesnt expose any of the info, i actually do a bunch of postprocessing to hide frames that dont matter

untold basin
untold basin
trim seal
#

damn rust really is a pain to work wth

untold basin
#

and a whole bunch of my kernel's helper APIs dont show up either

#

so for example sk_kern::mmio::{read, write}{8, 16, 32, 64} will never be visible on a stacktrace because i check for that and hide them

trim seal
#

why?

untold basin
# trim seal why?

size + those functions are inline(always) and they take the address from the caller

#

and it looks ugly in a stacktrace tbh

untold basin
#

if i want to know what exactly caused the issue i have a decompiler

trim seal
#

isnt it useful to know it was mmio that caused a fault?

#

instead of having to read a disassembly

untold basin
#

well it cant cause kasan errors so

untold basin
#

and without a file name/line number its pretty hard to tell anyway

trim seal
untold basin
#

each entry takes a few bytes

trim seal
#

how many symbols do u have lol

untold basin
#

not a lot lmao

vapid echo
#

Infy one thing I wanna try is your fallible instructions

#

That's cool

#

pitust are you gonna do that too

trim seal
#

yeah imo that shit is super convenient

untold basin
#

90kb of unwind data total

#

less when its compressed (and my kernel is compressed, its self-decompressing)

untold basin
vapid echo
#

MSR ones I think?

trim seal
#

i currently have abortable instructions for MSRs and try_memcpy

untold basin
#

uhhhhh is that even reliable

untold basin
#

lol

trim seal
#

currently only used in unwind code

untold basin
#

oh

vapid echo
untold basin
untold basin
trim seal
#

yeah its just a building block so ill totally use it for copy_to_user also

vapid echo
#

I'm not sure how it works

untold basin
#

using that for copy to/from user is a bad idea

trim seal
#

but it needs more work ofc, like enabling access to user memory etc

#

and validation

untold basin
#

arm supremacy: i dont need to do validation

trim seal
untold basin
trim seal
#

ohh

vapid echo
#

You have to handle exceptions though? Do you have a list of fallible functions and on exceptions you check whether or not it was in that?

trim seal
#

he probably has adhoc code to handle that specific one

vapid echo
#

No I mean you

untold basin
trim seal
untold basin
#

thats what the adr x8, .kasan_fail is for

trim seal
#

just read the code lol

vapid echo
#

Yeah ok fine meme

untold basin
# untold basin thats what the `adr x8, .kasan_fail` is for
    # Save x0 and x1
    sub sp, sp, 0x10
    stp x0, x1, [sp, 0x00]

    # Check if ELR_EL1 is in the usercopy region
    mrs x0, ELR_EL1
    adr x1, arm64_usercopy_crit_start
    cmp x0, x1
    b.lo 3f
    adr x1, arm64_usercopy_crit_end
    cmp x0, x1
    b.hs 3f

    # If so, fix stack and return to x8
    add sp, sp, 0x10
    msr ELR_EL1, x8
    eret
``` i have special asm for this even in the exception path
vapid echo
#

Ah cool

untold basin
#

does x86 guarantee that unknown MSRs fail with any specific exception

trim seal
#

yeah, GPF

untold basin
#

ah yes

#

okay neat

trim seal
#

GPF is also used as -EINVAL

#

as like a bad input value for an MSR

untold basin
#

wait wtf does your code do lmao

trim seal
#

i mean x86

#

if u try to wrmsr something bad u get a gpf

#

even if the msr is known

#

so GPF is both bad input and/or unknown msr

untold basin
#

ah i see

#

although i think that ```c
if (ai->flags & ABORTABLE_INSTRUCTION_ENOSYS_ON_ERROR)
registers_set_return_value(regs, ENOSYS);
else if (ai->flags & ABORTABLE_INSTRUCTION_EFAULT_ON_ERROR)
registers_set_return_value(regs, EFAULT);

trim seal
#

why

untold basin
#

idk maybe it makes more sense in your impl

trim seal
#

all it does is reduce code duplication

untold basin
#

i just handle this outside of the asm helper

#

and the entire mechanism is private to the arm64 port

#

oh yeah i have something that vaguely approximates a strong separation of generic vs per-isa code

trim seal
#

yeah i mean all it does is it makes it so u dont need a wrapper

trim seal
#

at least looking at how it scales in linux

untold basin
#

in my case i need a wrapper anyway

trim seal
#

which i stole the thing from

trim seal
untold basin
#

because i want to handle dispatching faults to the right process manually

trim seal
#

wdym by dispatching faults

tawdry surge
#

imagine if there was a way for the arch to reject unsupported writes without an exception

untold basin
tawdry surge
#

intel apparently can't imagine this

untold basin
#

riscv cant do that either i think

#

right?

trim seal
untold basin
#

intel has nothing, arm has ldtr, riscv has ? (ofc depending on your extensions)

trim seal
untold basin
#

so its a bit of an architecture thing

trim seal
#

like u handle copy to user errors and normal user faults in the same way?

untold basin
#

no

tawdry surge
#

it (riscv) can, it has write-any read-valid registers as well

untold basin
#

i in general don't take [recoverable] kernel exceptions other than uaccess right now

trim seal
#

ah ok

untold basin
untold basin
tawdry surge
#

recoverable kernel exceptions except for very few cases are a mistake meme

trim seal
#

make a kernel where every kernel exception is recoverable

untold basin
#

mine are only recoverable in uaccess

#

and in the smp trampoline

#

and i dont even have an error message otherwise

untold basin
tawdry surge
#

why does the smp trampoline need recoverable exceptions?

trim seal
#

lol

untold basin
tawdry surge
untold basin
#

and in general my translation is configured to writable implies execute never

#

and you cant execute from the direct map

#

you cant enable translation only for higher half

#

so if you don't take an exception, you are kinda stuck:

  • you need to enable translation
  • you need to be somewhere where after enabling translation, you will map to the same place in physmem
  • you need to be somewhere which is not covered by any page table
  • and the kernel is not identity mapped
tawdry surge
#

i don't get it

untold basin
#

well

#

the smp trampoline needs to be executable

tawdry surge
#

you just identity map the page that the stub is located on

#

in the lower half

untold basin
tawdry surge
#

in a specifically crafted address space

untold basin
#

but yeah i guess that works too

trim seal
#

so u manually fixup the pc of the cores when they start up or something?

untold basin
#

you can also just intentionally take a prefetch abort

tawdry surge
#

it's way less janky than catching a pf lol

untold basin
trim seal
#

crazy stuff

untold basin
#

and then intentionally take either a prefetch abort or an undefined instruction

#

depending on how hard the core OoOs

tawdry surge
#

we just treat it as a user page space except that we map it as a kernel page

untold basin
#

the other annoying issue for me is that i dont really have a good allocator to use

#

allocator bringup occurs after i do smp

#

i have an early bootstrap bump allocator for smp, but that can't free

untold basin
#

what i do is definitely simpler

tawdry surge
#

well you need the infrastructure anyway if you want to run userspace :^)

untold basin
#

the userspace stuff only works after you have the memory allocator initialized

tawdry surge
#

true. but there's also no need to do smp before allocation

untold basin
#

uhhh kinda

#

i need to know at least how many CPUs there are

#

because my allocator needs to allocate per-cpu structures

#

i could split it up

trim seal
#

u can kinda parse that info and not start cpus at the same time lmao

tawdry surge
#

we enumerate CPUs before allocation but launch them afterwards

untold basin
#

and there is zero benefit from doing so

untold basin
#

okay and now im back to memory allocator hell

#

so i kinda want to allocate dma buffers

#

and like i could make them discontinous and say you cant allocate dma buffers larger than 1 page

#

hmm maybe i should just do that lol

untold basin
#

ugh okay i decided to work on sync instead

#

and now im not sure how to implement sync lmao

#

i dont love the idea of taking a sleep lock

tawdry surge
#

sync?

#

as in synchronization data structures?

#

or sync as in fsync()

untold basin
#

not fsync lol im nowhere near disk drivers

tawdry surge
#

what synchronization primitive do you want to implement?

#

you're writing in rust, right?

#

just do a block_on(future) :^)

untold basin
#

lmao

tawdry surge
#

that's what iretq did for thor-rs

untold basin
#

futures are bad tho

#

they perform not great iirc

#

and they also dont match the rest of my model

untold basin
#

or an MPSC queue or something

tawdry surge
#

a semaphore sounds like a good start

#

and can basically be used as a primitive for everything else

untold basin
#

i mean go does that

#

okay ill just go steal more code and ideas from go

#
// Puts the current goroutine into a waiting state and calls unlockf on the
// system stack.
//
// If unlockf returns false, the goroutine is resumed.
//
// unlockf must not access this G's stack, as it may be moved between
// the call to gopark and the call to unlockf.
//
// Note that because unlockf is called after putting the G into a waiting
// state, the G may have already been readied by the time unlockf is called
// unless there is external synchronization preventing the G from being
// readied. If unlockf returns false, it must guarantee that the G cannot be
// externally readied.
//
// Reason explains why the goroutine has been parked. It is displayed in stack
// traces and heap dumps. Reasons should be unique and descriptive. Do not
// re-use reasons, add new ones.

awesome

#

so yeah go unlocks the semaphore bucket lock on the scheduler stack

tawdry surge
#

for what?

untold basin
#

go "semaphores" are basically implemented as userspace futexes

#

and go does its own userspace scheduling

tawdry surge
#

for low leveling blocking i'd just add Thread::park(), Thread::unpark()

untold basin
tawdry surge
#

it's basically equivalent to a binary semaphore attached to each thread

open osprey
untold basin
#

it still is i think

open osprey
#

rust had green threads as well

#

so the first i saw of rust i took it to be basically a golike language and i still have the shadow of that prejudice in my mind

untold basin
#

so now taking a lock means you take a semaphore, which acquires a lock, then you take from a binary semaphore, which acquires another lock

tawdry surge
#

why does taking a semaphore take a lock?

untold basin
#

well ig depends on the impl

#

oh wait no

#

yeah to enqueue yourself on the wait list you need to take a lock over the wait list

tawdry surge
#

ah a spinlock

untold basin
#

well yeah

untold basin
untold basin
#

ok i have something which may be a correct semaphore

#
use core::{
    hint::{likely, unlikely},
    sync::atomic::Ordering,
};

use crate::{
    atomic::Atomic,
    ex::sync::turnstile::{ParkToken, UnparkToken, park, unpark_one},
};

pub struct Semaphore {
    value: Atomic<u64>,
}
impl Semaphore {
    pub const fn new(value: u64) -> Semaphore {
        assert!(value < 0x8000_0000_0000_0000);
        Semaphore {
            value: Atomic::new(value << 1),
        }
    }
    unsafe fn put_slow(&self) {
        unsafe {
            let _ = unpark_one((&raw const self.value) as usize, |_, r| {
                debug_assert_eq!(r.unparked_threads, 1);
                if !r.have_more_threads {
                    self.value.fetch_sub(1, Ordering::Relaxed);
                }
                UnparkToken(0)
            });
        }
    }
    pub fn put(&self) {
        let value = self.value.fetch_add(2, Ordering::Release);
        if unlikely(value & 1 != 0) {
            unsafe { self.put_slow() };
        }
    }
    pub fn get(&self) {
        loop {
            let value = self.value.load(Ordering::Relaxed);

            if value < 2 {
                unsafe {
                    park((&raw const self.value) as usize, ParkToken(0), |_| {
                        if self.value.load(Ordering::Relaxed) < 2 {
                            self.value.fetch_or(1, Ordering::Relaxed);
                            true
                        } else {
                            false
                        }
                    })
                };
                continue;
            }
            if likely(
                self.value
                    .compare_exchange_weak(value, value - 2, Ordering::Acquire, Ordering::Relaxed)
                    .is_ok(),
            ) {
                return;
            }
        }
    }
}
#

now is it a correct semaphore? lol idk

#

it feels right

#

dies immediately in miri when testing basically this impl against parking_lot_core

#
error: Undefined Behavior: Data race detected between (1) non-atomic write on thread `unnamed-2` and (2) non-atomic read on thread `unnamed-1` at alloc7
  --> src/main.rs:80:44
   |
80 |                 unsafe { VALUE.get().write(VALUE.get().read() + 1) };
   |                                            ^^^^^^^^^^^^^^^^^^ (2) just happened here
   |
help: and (1) occurred earlier here
  --> src/main.rs:80:26
   |
80 |                 unsafe { VALUE.get().write(VALUE.get().read() + 1) };
   |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
   = note: this is on thread `unnamed-1`
note: the current function got called indirectly due to this code
  --> src/main.rs:77:9
   |
77 | /         thread::spawn(|| {
78 | |             loop {
79 | |                 SEMA.get();
80 | |                 unsafe { VALUE.get().write(VALUE.get().read() + 1) };
...  |
83 | |         });
   | |__________^
``` hmm yes
#

ok it turns out its just me dumb

#

lol

#

ok and now my turnstile/parkinglot knockoff is broken

tawdry surge
#

Didn't you say that you don't want to do a parking lot knockoff? Or did you do it on top of semaphores as a basis?

untold basin
#

anyway i did that so

tawdry surge
#

You did say that you dislike the idea of using park() and unpark() as primitive

untold basin
#

not as in parking_lot_core::{park, unpark_one, unpark_all}

tawdry surge
#

Ah

#

Makes sense

untold basin
#

okay ive got myself some one byte sleeping locks

untold basin
tawdry surge
#

lol

untold basin
#

and its a lot easier to test the primitives too

untold basin
#

hmm does it even make sense to use a queued lock to protect my parkinglot meme's hash buckets

#

because disabling preemption is dirt cheap

trim seal
#

depends how your per-cpu is implemented meme

trim seal
#

you may have 50 indirections, debug checks, asserts, etc

#

because of rust

untold basin
# trim seal you may have 50 indirections, debug checks, asserts, etc

the fast path is this:```arm
ffffffff0000dd8c fe4fbfa9 stp lr, x19, [sp, #-0x10]! {__saved_lr} {__saved_x19}
ffffffff0000dd90 481640b9 ldr w8, [x18, #0x14]
ffffffff0000dd94 88030034 cbz w8, 0xffffffff0000de04

ffffffff0000dd98 08050051 sub w8, w8, #0x1
ffffffff0000dd9c 481600b9 str w8, [x18, #0x14]
ffffffff0000dda0 68000034 cbz w8, 0xffffffff0000ddac

ffffffff0000dda4 fe4fc1a8 ldp lr, x19, [sp], #0x10 {__saved_x19}
ffffffff0000dda8 c0035fd6 ret


pretend this got inlined
trim seal
#

yeah im not qualified to judge arm per-cpu

untold basin
#

you can count the number of instructions trl

untold basin
trim seal
#

on x86 a good per-cpu would be inc gs:preempt_disable_count[rip]

trim seal
#

why a branch?

untold basin
#

you need to check for pending preemption

trim seal
#

for enable

untold basin
#

oh sorry this is the enable path

trim seal
#

ah ok

untold basin
#

disable does get inlined

#

its 3 instructions: ```arm
ffffffff0000e30c 481640b9 ldr w8, [x18, #0x14]
ffffffff0000e310 08050011 add w8, w8, #0x1
ffffffff0000e314 481600b9 str w8, [x18, #0x14]
ffffffff0000e318 c0035fd6 ret

#

read, add, write

trim seal
#

whats in x18?

untold basin
#

percpu data

trim seal
#

is that like

#

reserved in the abi?

untold basin
untold basin
#

but -ffixed-[reg] makes whatever you want reserved ๐Ÿ™ƒ

trim seal
#

u pass that right?

untold basin
#

yes ofc lmao

trim seal
#

lol

untold basin
#

its not reserved on linux

#

The role of register r18 is platform specific. If a platform ABI has need of a dedicated general-purpose register to carry inter-procedural state (for example, the thread context) then it should use this register for that purpose. If the platform ABI has no such requirements, then it should use r18 as an additional Caller-saved register. The platform ABI specification must document the usage for this register.

trim seal
#

iirc linux stores per-cpu base in el0 sp

untold basin
trim seal
#

oh

#

what about per-cpu base

untold basin
#

i have no idea

trim seal
#

oh that might actually always do per_cpu_base[this_cpu_id]

untold basin
#

its in tpidr_el1

trim seal
#

base or just the id?

untold basin
trim seal
#

ah ok

#

why dont u use tpidr_e1?

untold basin
#

i do

#

it contains a mirror of x18

trim seal
#

lol

#

whats the point?

untold basin
#

it lets you manipulate percpu data without reading from a register

#

i need a copy because i need to restore it after running userspace

trim seal
#

oh ok

untold basin
#

i also store the current CPU number in TPIDRRO_EL0

#

(which is readable by userspace)

trim seal
#

makes sense

untold basin
#

i guess i could set x18=current task or something but idk

untold basin
#

which is kinda annoying

trim seal
#

hmm not really

#

__seg_gs

untold basin
#

im writing rust

trim seal
#

ah

#

right

untold basin
#

also using seg_gs like that is definitely UB lol

trim seal
#

how lol

untold basin
#

actually maybe its not if you use an atomic

#

uhhh id have to think about the legality of that but it does not feel super robust lol

trim seal
#

it's not allowed to cache gs: stuff

untold basin
trim seal
#

by being able to compile linux

#

lmao

untold basin
#

thats not a promise

#

linux does a lot of UB lmao

trim seal
#

but yeah i havent read the docs, but iirc it was defined to not be cached

#

or so i heard somewhere

untold basin
#

idk i cant find anything about that

#

and it would be super dumb if this was a generic rule

#

this is used on amdgpu and nvptx for like normal memory

untold basin
#

and thats the result on clang

#

(this is subject to normal strict aliasing btw)

trim seal
#

i mean its ub to do per cpu writes/reads without preempt_disable

#

so this doesnt break anything

untold basin
#

thats not the point

tawdry surge
#

It'd be surprised if this shows up in any meaningful benchmarks

untold basin
#

if you want even cheaper, a load followed by a store of 1 is even cheaper

#

instead of a counter

#

but everyone parrots linux including me and has a counter because ???

#

i do want to get rid of the counter (because its really dumb) but i didnt get around to it

trim seal
untold basin
trim seal
#

its just a general race-is-ub rule

untold basin
#

no this is strict aliasing

trim seal
#

sure, but whats your point?

untold basin
#

you cant get it to emit an inc without asm

#

afaict

#

you can get a lock inc, which i guarantee is worse than a load+add+store

tawdry surge
#

I think linux is the only mainstream OS with a preempt disable counter (?)

untold basin
#

which has only 20 cycles of latency on alderlake!

untold basin
#

its a lot more common in hobby OSes though

tawdry surge
#

yeah

trim seal
#

do u just not allow nested preempt disable calls?

untold basin
tawdry surge
#

you save the previous state

trim seal
#

why would u do that if u can do less work with a counter?

untold basin
untold basin
# trim seal

this is literally 10x slower than read+add+write btw

tawdry surge
#

why would you use lock

untold basin
trim seal
#

yeah

untold basin
#

so you need per-isa porting work

tawdry surge
#

well, just use asm

#

yeah

trim seal
#

if u want this optimized you will need asm

trim seal
#

u can have a generic impl

untold basin
#

or you can just do a flag which only needs generic loads and stores

trim seal
#

and an isa can override it

tawdry surge
#

you can just use relaxed loads/stores instead of inc

#

there's 100% no measurable overhead

untold basin
#

if you lock inc, there is

trim seal
#

relaxed still uses a lock

tawdry surge
#

yeah if you're lock, that's bad

untold basin
#

you do a relaxed load, add, relaxed store

trim seal
#

well im looking at compiler exporer???

tawdry surge
#

relaxed load/store, not relaxed fetch_add

trim seal
tawdry surge
#

no, why?

trim seal
#

otherwise u increment preempt disable on a random cpu

untold basin
#

if you can get preempted then preempt_count == 0

trim seal
#

what

untold basin
#

the store is single-instruction atomic

tawdry surge
#

yeah

#

pitust is correct, that's also how all non-linux OSes do it

trim seal
#
cur_count = read(preempt_count)
cur_count += 1;
write(preempt_count, cur_count)
#

u can get preempted at any point before write

tawdry surge
#

you don't do a counter

#

just 0/1

trim seal
#

well ok in that case yes

#

but that still doesnt work

tawdry surge
#

you cannot get preempted unless the disable flag is "committed"

untold basin
trim seal
#

because u dont know if it was disabled before

tawdry surge
#

yes you do, because context switch preserves the bit

trim seal
#

or well i guess if u ended up on a different cpu u know it was enabled on that cpu

tawdry surge
#

you cannot get switched in with preempt disabled

#

that'd break all kinds of stuff

trim seal
#

yeah i suppose it works if u have a boolean

untold basin
#

it works with a counter too

trim seal
#

so if u read and it returns 0, you might end up on a different cpu, but u know its 0 there to because u got switched to it in the first place

untold basin
#

yes

trim seal
#

it doesnt work with a counter

untold basin
#

it does

trim seal
#

oh ig same logic applies lol

untold basin
#

a flag is still better though

trim seal
#

a flag?

untold basin
#

sorry

#

bool flag > counter

trim seal
#

why

tawdry surge
#

it also works with a counter

#

because only the 0->1 transition is relevant

trim seal
#

u have to allocate space for the flag on every call stack

#

it makes no sense

tawdry surge
#

if it's non-zero already, you cannot change cpus

trim seal
#

if u can optimize with a counter

#

yes, everything that disables preemption

tawdry surge
#

and for the 0-1 transition, the same argument applies

untold basin
#

oh i guess to save the old flag

trim seal
#

yes

untold basin
trim seal
#

every function you may call into

untold basin
#

i mean im considering banning nested preempt_disable

trim seal
#

sure

untold basin
tawdry surge
#

optmizing a load+store to an inc is 100% larp

trim seal
#

you will just end up with

do_thing()
do_thing_with_preemption_already_disalbed()
tawdry surge
#

It doesn't offer any real benefit but it has real costs

untold basin
tawdry surge
#

namely that it becomes arch specific

untold basin
#

inc has 3 uops

tawdry surge
#

yeah, i doubt that you can show me a single benchmark where it makes a difference that is not within noise

untold basin
#

so its just a rmw

trim seal
#

yeah inc internally probably schedules the same sequence

tawdry surge
#

on any competent CPU, the only difference is code size

trim seal
#

its just less code bytes

untold basin
#

oh wait no on zen5

untold basin
#

seems like on zen5 it might be a bit cheaper

#

but its not a measurable difference regardless its like 1 vs 3 cycles

untold basin
trim seal
#

technically u do run that on hot paths

#

in the scheduler etc

untold basin
#

my scheduler runs with irqs masked

trim seal
#

fair ig lol

untold basin
#

i dont want to take irqs mid context switch

tawdry surge
untold basin
#

its also not a hot code path

tawdry surge
#

iret takes what, 1000 cycles?

untold basin
trim seal
#

eretu trl

tawdry surge
#

so if you reduce the preempt disable by 1 cycle you gain at most 0.1%

untold basin
#

idk on m1 a linux syscall is like 300

tawdry surge
#

in reality the irq does mode than just iret

#

yeah, a syscall is cheap

#

an irq is really expensive on x86

untold basin
#

why? iret?

tawdry surge
#

yeah

trim seal
#

is it a property of x86 tho?

untold basin
#

just iret using sysretq /j

trim seal
#

Dont u have to do something similar to iret on other arches

tawdry surge
#

#x86 message

untold basin
#

an arm64 eret is <300 cycles

tawdry surge
#

when i was testing thread register restore code paths, replaced sysretq by iretq for testing

untold basin
#

so its not as bad

trim seal
#

is there data on how expensive fred returns are?

tawdry surge
trim seal
#

is that some sort of a no-op syscall

tawdry surge
#

yes

trim seal
#

oh ok lol

tawdry surge
#

not just 2x slower than sysretq, 2x slower than kernel entry, syscall handler dispatch, ipl checks, preemption and migration checks, kernel exit combined

untold basin
#

ok anyway going back to my earlier problem

should i just use a spinlock for this lol

#

i think thats probably a better idea than a complicated pushlock-style mechanism

untold basin
#

locking

vapid echo
#

Yes ok but what

untold basin
#

like im doing generic locking infrastructure

vapid echo
#

But what is "this"

untold basin
#

so basically im doing a ripoff of the wtf::parkinglot/turnstiles/futexes/go semaphores family

#

where you have a bunch of buckets with their own lock which you use to queue on (so that the inline sync primitive is smaller)

vapid echo
#

ah yeah typically you do have a spinlock over the buckets

#

if you mean the table hashed by the lock address

untold basin
#

yeah

#

WTF::ParkingLot and rust parking_lot take a sleeping lock

vapid echo
#

But you might not be able to do that if you implement your sleeping lock using that

untold basin
#

which is larger

vapid echo
#

Yes but is it worth it?

untold basin
#

lol i have no idea

#

how would you even measure that

#

my vibe is no though

#

not for a kernel

vapid echo
#

I'd say the lock likely won't experience that much contention

#

And it's a quick critical section

untold basin
#

if you disable preemption at least

#

yeah

vapid echo
#

Yea

#

but you kinda have to disable preemption

#

Huh reading about parking lot it's basically turnstiles

untold basin
#

yes!

#

wait until you read about plan9 semaphores

vapid echo
#

And they also call not doing handoff barging

tawdry surge
#

does parking lot / futex style locking make sense inside the kernel?

#

or are we talking about futexes for userspace?

vapid echo
#

Also with turnstiles you can do priority inheritance

tawdry surge
#

But parking lot is not entirely like a turnstile, isn't it?

#

because it doesn't actually use the key as a pointer

#

I think BSD-style turnstiles make more sense

#

well i guess parking lot is just the blocking side of a turnstile lock

#

if you think of it that way

#

and then it makes sense, yeah

untold basin
#

awesome, i started to test my impl in miri

#

that was fast lol

#

ok apparantly my lock is broken???

#

yeah i used a fucking relaxed store when unlocking

#

me dumb

trim seal
untold basin
#

miri?

#

its by definition reliable for all rust programs nvm apparantly thats not the case

#

(well obviously it will have false negatives. but its guaranteed not to have false positives)

trim seal
#

does it work with barriers

untold basin
#

uhhh i actually dont know

#

yes i think so

tawdry surge
#

it is reliable as long as all the code is written in rust

#

it cannot reason across FFI boundaries of course

untold basin
#

i think it can somewhat do FFI now

#

but yeah obv its only useful if you have "normal" userspace code

tawdry surge
#

so it's rather slow / not usable for full programs

#

but it does detect races and other memory unsafety issues

untold basin
#

it detects basically all violations of rust's memory access rules

untold basin
tawdry surge
#

yeah but it makes much more sense to run unit tests etc through miri than full programs

untold basin
#

yeah

untold basin
#

okay im now trying to figure out a good ipc model

i still havent come up with a good one which isnt pain to implement

tawdry surge
#

have you looked at existing ones?

untold basin
#

a bit

#

so the big thing is that i want to make it work without dynamic allocation (i.e. allocation when opening a connection or setting up a server is fine, but anything else should be preallocated)

and ideally it would be asynchronous

tawdry surge
#

asynchronous and no allocation kinda contracts each other, doesn't it?

untold basin
#

well, lets call it "optimistically asynchronous"

tawdry surge
#

unless you preallocate a bunch of inflight ipc data structures

untold basin
#

and then you block if you cant allocate more

#

and if you can then you can obviously get more concurrency

#

im gonna look at managarm maybe

#

uhhhhh GCEs are not a good thing to rely on

#

they are more like a "this is getting removed possibly in a few months" kind of feature

#

not a "this is getting stabilized hopefully soonish" kind of feature

tawdry surge
#

iirc our use of generic const expressions can be rewritten at the cost of making it more annoying / maybe using a proc macro

untold basin
#

yea i see i see

#

assuming the only use is submit_async/Action, that looks easy enough to solve

tawdry surge
#

yeah i think that's the only place

#

i think it's really only for the construction of the array

#

in let mut actions = [const { MaybeUninit::uninit() }; T::ACTION_COUNT];

untold basin
#

also here is some functions in the managarm code i was just curious how you handle reading from shared memory

#

oop wrong paste

#

here is the correct 3rd picture

#

(you cannot just blind memcpy from random user-accessible pointers lmao)

#

this is probably not an issue but like

#

jfc

#

oh and also this is a controlled allocation primitive with whatever data you want on the only kernel zone, my favorite

#

at least my kernel attempts to do type isolation

#

even if it doesnt try super hard

tawdry surge
#

wdym

#

there is no copy from user here

untold basin
#

which you also cant just memcpy from safely

tawdry surge
#

ah you mean because userspace could technically write and then its technically a data race?

#

yeah that's true

tawdry surge
#

but there's no way around that in current c++

untold basin
#

in rust i'd tell you to do by-value freeze

tawdry surge
#

unless you hand roll and asm memcpy i guess

untold basin
#

which some people regard as being sound

tawdry surge
#

there was a proposal to standardize bytewise atomic memcpy but it was not accepted

untold basin
#

just do like asm volatile("" :: "r"(&the_object)); lol

#

that is sound within llvm

#

because data races are not llvm ub

tawdry surge
#

but there is no TOCTOU here right? userspace cannot change the count parameter post hoc, neither can it invalidate the buffer

#

it can just influence the buffer contents

untold basin
#

imagine the compiler decided to remove the memcpy and read from the mapping directly (which is sound)

#

im not sure if you can prove that the stuff in the middle will have no side effects on memory, it could be enough of a barrier in practice

tawdry surge
#

ah i see what you mean. that's true

untold basin
tawdry surge
untold basin
#

there is a very funny strategy here i think

#

i THINK llvm might have intrinsics that let you do this

#

that arent surfaced

trim seal
tawdry surge
#

how did you implement it?

trim seal
#

it literally did relaxed loads on each byte

#

of source

tawdry surge
#

that probably has horrible codegen though

untold basin
#

yeah

trim seal
#

i dont know how else to solve it but yes

untold basin
#

definitely worse than doing the mildly UB thing of memcpy then by-value freeze

untold basin
#

which one

#

by-value freeze?

trim seal
#

yeah\

untold basin
#

ok so rust has a concept of a "byte" which has 257* values: 0-255, inclusive, and a special value called "uninit"

#

the simplest case of by-value freeze is when you go from a value of type "u8 | uninit" to a value of type "u8" by doing something like ```rs
fn by_value_freeze_u8(value: u8 | uninit) -> u8:
if is_uninint(value):
return unpredictable_byte()
else:
return value


(not real code ofc)
#

its called "freeze" because LLVM has an instruction called freeze which does exactly this

untold basin
#

now the UB

#

in LLVM, data races are permitted: when two stores race, you store an LLVM undef value (which is similar to a rust uninit byte); when a load races with a store, you load an LLVM undef value

#

oh llvm has a real byte type now

#

instead of a weird and not used for good reason hack of <i1 x N>

trim seal
# tawdry surge that probably has horrible codegen though
ffffffff800041e4:       48 83 e2 fc             and    rdx,0xfffffffffffffffc
ffffffff800041e8:       31 c9                   xor    ecx,ecx
ffffffff800041ea:       66 0f 1f 44 00 00       nop    WORD PTR [rax+rax*1+0x0]
ffffffff800041f0:       44 0f b6 04 0e          movzx  r8d,BYTE PTR [rsi+rcx*1]
ffffffff800041f5:       44 88 04 0f             mov    BYTE PTR [rdi+rcx*1],r8b
ffffffff800041f9:       44 0f b6 44 0e 01       movzx  r8d,BYTE PTR [rsi+rcx*1+0x1]
ffffffff800041ff:       44 88 44 0f 01          mov    BYTE PTR [rdi+rcx*1+0x1],r8b
ffffffff80004204:       44 0f b6 44 0e 02       movzx  r8d,BYTE PTR [rsi+rcx*1+0x2]
ffffffff8000420a:       44 88 44 0f 02          mov    BYTE PTR [rdi+rcx*1+0x2],r8b
ffffffff8000420f:       44 0f b6 44 0e 03       movzx  r8d,BYTE PTR [rsi+rcx*1+0x3]
ffffffff80004215:       44 88 44 0f 03          mov    BYTE PTR [rdi+rcx*1+0x3],r8b
ffffffff8000421a:       48 83 c1 04             add    rcx,0x4
ffffffff8000421e:       48 39 ca                cmp    rdx,rcx
ffffffff80004221:       75 cd                   jne    ffffffff800041f0 <atomic_src_memcpy+0x20>
ffffffff80004223:       48 85 c0                test   rax,rax
ffffffff80004226:       74 17                   je     ffffffff8000423f <atomic_src_memcpy+0x6f>
ffffffff80004228:       48 01 cf                add    rdi,rcx
ffffffff8000422b:       48 01 ce                add    rsi,rcx
ffffffff8000422e:       31 c9                   xor    ecx,ecx
ffffffff80004230:       0f b6 14 0e             movzx  edx,BYTE PTR [rsi+rcx*1]
ffffffff80004234:       88 14 0f                mov    BYTE PTR [rdi+rcx*1],dl
ffffffff80004237:       48 ff c1                inc    rcx
ffffffff8000423a:       48 39 c8                cmp    rax,rcx
ffffffff8000423d:       75 f1                   jne    ffffffff80004230 <atomic_src_memcpy+0x60>
ffffffff8000423f:       c3                      ret

It did this at O3

#

if i force noinline

trim seal
#

what is it even doing there

tawdry surge
#

unrolling 4 iters

#

but not coalescing the loads

trim seal
#

ah

trim seal
untold basin
#

you know at the end if you had a race or not

#

so if you know you didnt, then you know you can safely read all of the bytes

#

actually you dont even need by value freeze lmao me dumb

#

well you need it in the managarm case you dont need it in the ringbuffer case

tawdry surge
#

I think memcpy + asm volatile ("" : : : "memory") should be enough in the Managarm case, isn't it?

#

since that forces llvm to assume that the shared buffer has now changed in unpredictable ways

untold basin
#

i mean it would make llvm not able to miscompile

#

but i'd also give it the pointer because i think that makes it also not llvm UB

#

(pointer to the copied buffer)

#

i guess that forces a "real" copy instead of reading into registers

tawdry surge
#

reading into registers is fine though

trim seal
#

instead of atomic memcpy

untold basin
trim seal
#

well

untold basin
#

its only a small dose, its not llvm ub

trim seal
#

lmao

#

maybe its gcc backend ub

untold basin
#

idk gcc is lame and bad anyway

#

just apply the linux strategy on gcc

trim seal
#

whats the linux strategy

#

-fno-strict-aliasing?

untold basin
#

complain if gcc produces correct code that breaks you because "standards shit just do the """obvious""" thing dumbass"

trim seal
#

lol

#

i mean if your standard makes it impossible to write a custom allocator it is kinda shit

untold basin
#

you can you just cant write a good one without writing insane code

#

also you can do it in c++

#

(not that the C++ features that let you work properly afaict)

trim seal
#

what did u have to do to make it ub free again

untold basin
#

launder or start_lifetime_as or placement new

#

or disable strict aliasing