diff --git a/src/darwin/frameworks/BUCK b/src/darwin/frameworks/BUCK index 8188ca059..3f34d6424 100644 --- a/src/darwin/frameworks/BUCK +++ b/src/darwin/frameworks/BUCK @@ -16998,6 +16998,7 @@ cc_objects( srcs = [ "Combine/src/Combine.m", "Combine/src/CombineMetadata.c", + "Combine/src/CombineSubject.c", "Combine/src/CombineSymbols.c", ], compiler_flags = [ diff --git a/src/darwin/frameworks/Combine/src/CombineMetadata.c b/src/darwin/frameworks/Combine/src/CombineMetadata.c index 752dc8039..59854738c 100644 --- a/src/darwin/frameworks/Combine/src/CombineMetadata.c +++ b/src/darwin/frameworks/Combine/src/CombineMetadata.c @@ -377,10 +377,15 @@ CiderMetadataResponse cider_combine_anycancellable_metadata_accessor(size_t requ */ extern const uintptr_t cider_combine_currentvaluesubject_descriptor[]; +/* CombineSubject.c owns the instance layout, so it owns the size the class has to declare. */ +extern size_t cider_combine_subject_instance_size(const void *output); + void *cider_combine_class_instantiate(const void *descriptor, const void *const *arguments, const void *pattern) { - void *metadata = cider_combine_build_class(descriptor, 16, arguments[0], arguments[1]); + void *metadata = cider_combine_build_class(descriptor, + cider_combine_subject_instance_size(arguments[0]), + arguments[0], arguments[1]); if (cider_combine_trace()) { fprintf(stderr, "CIDER_COMBINE class instantiate desc=%p pattern=%p args=%p,%p -> %p\n", diff --git a/src/darwin/frameworks/Combine/src/CombineSubject.c b/src/darwin/frameworks/Combine/src/CombineSubject.c new file mode 100644 index 000000000..893484a2a --- /dev/null +++ b/src/darwin/frameworks/Combine/src/CombineSubject.c @@ -0,0 +1,140 @@ +/* + * THE FIRST COMBINE FUNCTION WITH A BODY, rather than a name for the loader. + * + * Everything next door is SHAPE: descriptors, patterns, metadata. With all of it answered, iA Writer + * finally asked for BEHAVIOUR and landed in data, because a placeholder symbol is an address in + * __const and calling it executes a constant: + * + * rip inside Combine, at _$s7Combine19CurrentValueSubjectCyACyxq_Gxcfc + * + * which is CurrentValueSubject.init(_:). This file implements that initialiser and send(_:). + * + * HOW A SWIFT METHOD IS WRITTEN IN C. clang has the calling convention: swiftcall passes arguments + * as Swift does, and a parameter marked swift_context arrives in the context register, r13 on + * x86_64, which is the register a method's self uses. A generic parameter is ADDRESS ONLY from the + * callee's side, so Output is passed as a pointer and copied through its value witness table, which + * hangs one word below its metadata. Nothing here needs the Swift compiler. + * + * WHERE THE VALUE LIVES. A class instance starts with a sixteen byte heap object header, and this + * class has exactly one stored property, so the value goes straight after it. The instance size is + * not a constant: it depends on Output, which is known when the metadata is instantiated, so the + * builder next door asks this file for the size and writes it into the class. + * + * WHAT IT DOES NOT DO. There are no subscribers yet, so send(_:) updates the value and delivers it + * nowhere. The next functions, sink and the operators, are what make delivery mean anything. + */ + +#include +#include +#include +#include + +/* + * A value witness table: eight functions, then the layout. The order is the runtime's own and the + * offsets were confirmed against the application, which reads size at 0x40 to size a stack buffer. + */ +struct CiderWitnesses { + void *initializeBufferWithCopyOfBuffer; + void *destroy; + void *initializeWithCopy; + void *assignWithCopy; + void *initializeWithTake; + void *assignWithTake; + void *getEnumTagSinglePayload; + void *storeEnumTagSinglePayload; + size_t size; + size_t stride; + uint32_t flags; + uint32_t extraInhabitantCount; +}; + +typedef void *(*CiderCopyWitness)(void *destination, void *source, const void *self); + +/* The heap object header: an isa and a refcount, and the stored property follows it. */ +#define CIDER_COMBINE_SUBJECT_PAYLOAD 16 + +static const struct CiderWitnesses *cider_combine_witnesses(const void *metadata) +{ + const uintptr_t *words = (const uintptr_t *) metadata; + + return (const struct CiderWitnesses *) words[-1]; +} + +/* + * WHERE THE GENERIC ARGUMENTS SIT IN A CLASS METADATA. A non resilient class keeps its immediate + * members at (positive size in words - number of immediate members), and the generic arguments are + * the first of them. CurrentValueSubject's descriptor declares twelve positive words and two + * immediate members, so Output is word ten and Failure word eleven. + */ +static const void *cider_combine_subject_output(const void *self) +{ + const uintptr_t *metadata = *(const uintptr_t *const *) self; + + return (const void *) metadata[10]; +} + +size_t cider_combine_subject_instance_size(const void *output) +{ + if (output == NULL) { + return CIDER_COMBINE_SUBJECT_PAYLOAD; + } + return CIDER_COMBINE_SUBJECT_PAYLOAD + cider_combine_witnesses(output)->stride; +} + +static int cider_combine_subject_trace(void) +{ + static int on = -1; + + if (on < 0) { + const char *v = getenv("CIDER_TRACE_COMBINE"); + + on = (v != NULL && v[0] != '\0') ? 1 : 0; + } + return on; +} + +/* + * init(_ value: Output). The caller has already allocated the instance, which is what the lower case + * fc in the mangled name means, and the value is owned by us: take it rather than copy it. + */ +__attribute__((swiftcall)) void *cider_combine_subject_init(void *value, + void *self __attribute__((swift_context))) + __asm__("_$s7Combine19CurrentValueSubjectCyACyxq_Gxcfc"); + +__attribute__((swiftcall)) void *cider_combine_subject_init(void *value, + void *self __attribute__((swift_context))) +{ + const void *output = cider_combine_subject_output(self); + const struct CiderWitnesses *witnesses = cider_combine_witnesses(output); + char *payload = (char *) self + CIDER_COMBINE_SUBJECT_PAYLOAD; + + ((CiderCopyWitness) witnesses->initializeWithTake)(payload, value, output); + if (cider_combine_subject_trace()) { + fprintf(stderr, "CIDER_COMBINE subject init self=%p output=%p stride=%zu\n", + self, output, witnesses->stride); + fflush(stderr); + } + return self; +} + +/* + * send(_ value: Output). The parameter is borrowed, so the stored value is overwritten with a copy + * and the caller keeps its own. + */ +__attribute__((swiftcall)) void cider_combine_subject_send(void *value, + void *self __attribute__((swift_context))) + __asm__("_$s7Combine19CurrentValueSubjectC4sendyyxF"); + +__attribute__((swiftcall)) void cider_combine_subject_send(void *value, + void *self __attribute__((swift_context))) +{ + const void *output = cider_combine_subject_output(self); + const struct CiderWitnesses *witnesses = cider_combine_witnesses(output); + char *payload = (char *) self + CIDER_COMBINE_SUBJECT_PAYLOAD; + + ((CiderCopyWitness) witnesses->assignWithCopy)(payload, value, output); + if (cider_combine_subject_trace()) { + fprintf(stderr, "CIDER_COMBINE subject send self=%p output=%p\n", self, output); + fflush(stderr); + } +} diff --git a/src/darwin/frameworks/Combine/src/CombineSymbols.c b/src/darwin/frameworks/Combine/src/CombineSymbols.c index d83b2a8f4..ee9c943ba 100644 --- a/src/darwin/frameworks/Combine/src/CombineSymbols.c +++ b/src/darwin/frameworks/Combine/src/CombineSymbols.c @@ -51,8 +51,7 @@ CIDER_COMBINE_SYMBOL(13, "_$s7Combine14AnyCancellableC5store2inyShyACGz_tF"); CIDER_COMBINE_SYMBOL(14, "_$s7Combine14AnyCancellableC6cancelyyF"); CIDER_COMBINE_SYMBOL(17, "_$s7Combine14AnyCancellableCSHAAMc"); CIDER_COMBINE_SYMBOL(18, "_$s7Combine14AnyCancellableCSQAAMc"); -CIDER_COMBINE_SYMBOL(19, "_$s7Combine19CurrentValueSubjectC4sendyyxF"); -CIDER_COMBINE_SYMBOL(22, "_$s7Combine19CurrentValueSubjectCyACyxq_Gxcfc"); +/* 19 and 22 are gone: CurrentValueSubject's initialiser and send are real, in CombineSubject.c. */ CIDER_COMBINE_SYMBOL(23, "_$s7Combine19CurrentValueSubjectCyxq_GAA9PublisherAAMc"); CIDER_COMBINE_SYMBOL(24, "_$s7Combine9PublisherPAAE010eraseToAnyB0AA0eB0Vy6OutputQz7FailureQzGyF"); CIDER_COMBINE_SYMBOL(25, "_$s7Combine9PublisherPAAE3mapyAA10PublishersO3MapVy_xqd__Gqd__6OutputQzclF");