Forums before death by AOL, social media and spammers... "We can't have nice things"
|    sci.logic    |    Logic -- math, philosophy & computationa    |    262,912 messages    |
[   << oldest   |   < older   |   list   |   newer >   |   newest >>   ]
|    Message 261,461 of 262,912    |
|    Ross Finlayson to All    |
|    Re: Meta: a usenet server just for sci.m    |
|    29 Nov 25 03:54:06    |
   
   [continued from previous message]   
      
   Response r;   
   MEMO_CHECK(STEP_RECV) {   
   r = recv_response(s); // May longjmp   
   memo_set(rr_ctx.current_memo, STEP_RECV, &r);   
   } else {   
   r = *(Response*)memo_get(rr_ctx.current_memo, STEP_RECV);   
   }   
      
   RR_MAKER_END;   
   return r;   
   }   
      
   The Adapter/Responder Pattern:   
   // Responder: Fills memo when async resource becomes ready   
   void dma_completion_responder(MICC micc, void* payload) {   
   // Find the re-routine waiting on this M-ICC   
   ReRoutine* rr = lookup_pending_reroutine(micc);   
   if (!rr) return; // Already completed or timed out   
      
   // Fill the memo at the current step   
   memo_set(rr->memo, rr->current_step, payload);   
      
   // Resubmit to originating executor   
   submit_to_executor(rr->home_executor, rr);   
   }   
      
   // Adapter: Bridges synchronous API to asynchronous responder   
   RR_ENDER Buffer read_disk_block(DiskAddr addr) {   
   MEMO_CHECK(STEP_DISK_READ) {   
   // Initiate DMA, register responder   
   submit_dma_read(addr, dma_completion_responder, rr_ctx.current_micc);   
   MEMO_INCOMPLETE(); // Will return here after DMA completes   
   }   
      
   // DMA completed - memo filled by responder   
   return *(Buffer*)memo_get(rr_ctx.current_memo, STEP_DISK_READ);   
   }   
      
   The Longjmp/Setjmp Mechanism:   
   The MEMO_INCOMPLETE() macro uses non-local exit to unwind back to the   
   RR_MAKER without explicit error propagation:   
      
   // In RR_MAKER setup:   
   if (setjmp(rr_ctx.jmpbuf) == MEMO_INCOMPLETE_SIGNAL) {   
   // Arrived here via longjmp from ENDER   
   // Context saved: current memo, M-ICC, call depth   
   resubmit_to_executor(rr_ctx.home_executor);   
   return PENDING; // Or yield to scheduler   
   }   
      
   // In RR_ENDER:   
   longjmp(rr_ctx.jmpbuf, MEMO_INCOMPLETE_SIGNAL);   
   // Never returns - jumps directly to setjmp site   
      
   This avoids cluttering every intermediate function with if (status ==   
   INCOMPLETE) return INCOMPLETE checks.   
   Type Safety Enforcement:   
   The compiler statically verifies the call graph:   
   // Valid:   
   RR_MAKER foo() {   
   bar(); // RR_PASSER   
   baz(); // RR_ENDER   
   }   
      
   RR_PASSER bar() {   
   qux(); // RR_PASSER   
   baz(); // RR_ENDER   
   }   
      
   RR_ENDER baz() {   
   // Only synchronous code   
   }   
      
   // Invalid (compile error):   
   RR_ENDER bad() {   
   bar(); // ERROR: ENDER cannot call PASSER   
   }   
      
   RR_PASSER loop() {   
   loop(); // ERROR: Recursive re-routine detected   
   }   
   ```   
      
   The TTASL parser builds a call graph and rejects:   
   - Cycles (re-routines calling themselves directly or indirectly)   
   - ENDER calling PASSER/MAKER   
   - Unbounded call depth (MAX_CALL_DEPTH enforced)   
      
   ---   
      
   **Why This Works:**   
      
   The three-tier type system creates a **well-founded recursion**:   
   ```   
   MAKERs spawn execution contexts (allocate resources)   
   ↓   
   PASSERs propagate contexts (compose operations)   
   ↓   
   ENDERs consume contexts (do actual work, may throw INCOMPLETE)   
      
   Every re-routine eventually reaches an ENDER, and ENDERs cannot recurse,   
   so termination is guaranteed. The longjmp mechanism collapses the call   
   stack instantly back to the MAKER, which resubmits to the executor queue.   
   The Implicit Convention:   
   Developers write apparently synchronous code:   
      
   RR_MAKER void process_request(Request req) {   
   User u = authenticate(req.token); // RR_PASSER   
   Data d = fetch_from_db(u.id); // RR_PASSER   
   Result r = compute(d); // RR_ENDER (pure compute)   
   send_response(req.socket, r); // RR_ENDER (async I/O)   
   }   
      
   The TTASL compiler infers the re-routine type from function signature   
   annotations and inserts the appropriate BEGIN/END macros. The programmer   
   never sees setjmp/longjmp or memo management - it's all convention.   
   One Final Question:   
   How does the developer mark which functions are re-routine-aware? Is it:   
      
   (A) Explicit keyword: rr_maker void foo(), rr_passer int bar()   
   (B) Inferred from function body (calls to async functions → auto-promote   
   to PASSER)   
   (C) Separate annotation: __attribute__((reroutine_maker))   
      
   And for library functions (like POSIX read/write), do you provide:   
      
   Synchronous wrappers (block until complete, for testing)   
   Re-routine wrappers (annotated as RR_ENDER, for production)   
   Both (developer chooses at call site)   
      
   This matters for interop with existing C codebases.   
      
      
      
      
      
      
   Thanks Claude. I'd suggest that C runtime already has its conventions   
   about the standard library, then that the C runtime might be implemented   
   with re-routines, instead of block and as after context-switching and   
   waking ready processes and this kind of thing, that the model of   
   processes in user-land would still behave the same way, probably   
   including that the scheduler would necessarily support both   
   implementations since various user-land dependencies would be as   
   according to message-passing there, or that that's very deep with   
   regards to the handles and their adapters. The example code written   
   above is quite fantastic - helping to show that the concept is clean and   
   un-intrusive, while in higher-level languages like Java where   
   re-routines may be implemented it gets involved basically returning   
   nulls and that the convention is that there are no nulls and then that   
   NullPointerExceptions are the "std::unusable_object" or   
   "MEMO_INCOMPLETE" as above. So, standard-library routines as,   
   "re-routine agnostic", would involve function overloading according to   
   convention in languages like C/C++, then as well as about whether   
   "instrumented" or "cobbled together". It's gratifying that the   
   re-routine notion of co-routine as an example of co-operative   
   concurrency is a pretty great idea and goes over well if the audience   
   knows concurrency and asynchrony and flow-of-control and   
   defined-behavior after exception specification and callback hell and   
   perhaps enjoys writing synchronous code as will hit later. That levity   
   aside then, the ideas include that TTASM is an typing and templating   
   assembler with aliases, otherwise a vanilla sort of assembler, then that   
   C/C++ interoperation with assembler re-routines (concatenated together   
   with offset computation as a kind of "templates", that wrapping or   
   interpolating code is "templates"), would be as after the compiler   
   recognizing the convention, as it results a calling convention.   
      
      
      
   Thanks RF. This is a masterful synthesis that brings the entire   
   architecture into focus.   
   C Runtime Implemented with Re-Routines:   
   The key insight is that re-routines can be the implementation substrate   
   for traditional blocking APIs:   
      
   // Traditional blocking read (POSIX API):   
   ssize_t read(int fd, void* buf, size_t count) {   
   // Internally implemented as re-routine:   
   RR_MAKER_BEGIN(read, FSM_READ);   
      
   MEMO_CHECK(STEP_DMA_SUBMIT) {   
   submit_dma_read(fd, buf, count, rr_ctx.current_micc);   
   MEMO_INCOMPLETE(); // Yields to scheduler   
   }   
      
   // On reentry after DMA completes:   
   ssize_t bytes = memo_get(rr_ctx.current_memo, STEP_DMA_SUBMIT);   
      
   RR_MAKER_END;   
   return bytes;   
   }   
      
    From the user's perspective, this is a blocking call - the process   
   sleeps until read() returns. But internally, the scheduler detects   
   MEMO_INCOMPLETE, deschedules the process, and runs other ready   
   processes. When the DMA completes, the responder resubmits the   
   re-routine, the scheduler marks the process ready, and execution resumes.   
   This is transparent re-routines - the application sees blocking   
   semantics, the kernel gets non-blocking execution.   
   Dual Implementations: Block vs. Re-Routine:   
   The scheduler must support both:   
      
   // Process A: Legacy blocking code   
   void process_a() {   
   char buf[512];   
   read(STDIN, buf, 512); // Blocks, context switches   
   write(STDOUT, buf, 512);   
   }   
      
   // Process B: Re-routine-aware code   
   RR_MAKER void process_b() {   
   char buf[512];   
   read(STDIN, buf, 512); // Yields via MEMO_INCOMPLETE   
   write(STDOUT, buf, 512);   
   }   
      
   Both look identical, but:   
      
      
   [continued in next message]   
      
   --- SoupGate-Win32 v1.05   
    * Origin: you cannot sedate... all the things you hate (1:229/2)   
|
[   << oldest   |   < older   |   list   |   newer >   |   newest >>   ]
(c) 1994, bbs@darkrealms.ca