1 /*
2  * portP.h - Port private API
3  *
4  *   Copyright (c) 2013-2020  Shiro Kawai  <shiro@acm.org>
5  *
6  *   Redistribution and use in source and binary forms, with or without
7  *   modification, are permitted provided that the following conditions
8  *   are met:
9  *
10  *   1. Redistributions of source code must retain the above copyright
11  *      notice, this list of conditions and the following disclaimer.
12  *
13  *   2. Redistributions in binary form must reproduce the above copyright
14  *      notice, this list of conditions and the following disclaimer in the
15  *      documentation and/or other materials provided with the distribution.
16  *
17  *   3. Neither the name of the authors nor the names of its contributors
18  *      may be used to endorse or promote products derived from this
19  *      software without specific prior written permission.
20  *
21  *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24  *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25  *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26  *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
27  *   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
28  *   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
29  *   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
30  *   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31  *   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32  */
33 
34 #ifndef GAUCHE_PRIV_PORTP_H
35 #define GAUCHE_PRIV_PORTP_H
36 
37 #include "gauche/priv/writerP.h"
38 
39 /*================================================================
40  * Real port structure
41  *
42  *  ScmPort is actually ScmPortImpl.
43  */
44 
45 /*
46  * Regardless of the port type, the port structure caches at most
47  * one character, in order to realize `peek-char' (Scheme) or `Ungetc' (C)
48  * operation.   'scratch', 'scrcnt', and 'ungotten' fields are used for
49  * that purpose, and outside routine shouldn't touch these fields.
50  * See portapi.c for the detailed semantics.
51  *
52  * Supporting custom ports (r6rs, srfi-181) complicates the interaction
53  * between an 'ungotten' character and port position.  The position of custom
54  * ports can be arbitrary object, and it is opaque outside of the custom
55  * port implementation.  So the port layer can't adjust the position.
56  * Instead, peek-char/byte has to prefetch the position to remember.
57  */
58 
59 typedef struct ScmPortImplRec {
60     SCM_PORT_HEADER;
61 
62     char scratch[SCM_CHAR_MAX_BYTES]; /* incomplete buffer */
63 
64     ScmChar ungotten;           /* ungotten character.
65                                    SCM_CHAR_INVALID if empty. */
66     ScmObj savedPos;            /* When we peek-char/byte on custom port,
67                                    we need to cache the position. */
68 
69     ScmInternalFastlock lock;   /* for port mutex */
70     ScmVM *lockOwner;           /* for port mutex; owner of the lock */
71     int lockCount;              /* for port mutex; # of recursive locks */
72 
73     ScmWriteState *writeState;  /* used internally */
74 
75     /* Input counters.  these doesn't take account of ungetting and
76        seeking: Ungetting doesn't affect those counters (you can think
77        that ungetting are handled above the counting layer).
78        Seeking invalidates counters; if you seek, the values of the counters
79        become bogus.
80        We don't have character counter, since it is difficult to track
81        (read-line uses byte read; see Scm_ReadLine in portapi.c).
82      */
83     ScmSize line;               /* line counter */
84     ScmSize bytes;              /* byte counter */
85 
86     /* The source or the sink of the port.   Use specialized accessor
87        functions to retrieve one of those union members. */
88     union {
89         ScmPortBuffer buf;      /* buffered port */
90         ScmPortInputString istr;
91         ScmDString ostr;        /* output string port */
92         ScmPortVTable vt;       /* virtual port */
93     } src;
94 
95     /* Port attibutes.  Use Scm_PortAttr* API to access. */
96     ScmObj attrs;
97 
98 } ScmPortImpl;
99 
100 #define P_(p)   ((ScmPortImpl*)(p))
101 
102 /*================================================================
103  * Some private APIs
104  */
105 
106 SCM_EXTERN void Scm__InstallCodingAwarePortHook(ScmPort *(*)(ScmPort*, const char*));
107 
108 /* Windows-specific initialization */
109 #if defined(GAUCHE_WINDOWS)
110 void Scm__SetupPortsForWindows(int has_console);
111 #endif /*defined(GAUCHE_WINDOWS)*/
112 
113 #define PORT_WALKER_P(port) \
114     (SCM_PORTP(port) && (SCM_PORT(port)->flags & SCM_PORT_WALKING))
115 
116 #define PORT_WRITESS_P(port) \
117     (SCM_PORTP(port) && (SCM_PORT(port)->flags & SCM_PORT_WRITESS))
118 
119 #define PORT_RECURSIVE_P(port) \
120     (P_(port)->writeState != NULL)
121 
122 #define PORT_LOCK_OWNER_P(port, vm) \
123     (P_(port)->lockOwner == (vm))
124 
125 /* Internal intreface to retrieve src member.
126    For public use, we have Scm_PortBufferStruct() etc. */
127 #define PORT_BUF(port)     (&P_(port)->src.buf)
128 #define PORT_ISTR(port)    (&P_(port)->src.istr)
129 #define PORT_OSTR(port)    (&P_(port)->src.ostr)
130 #define PORT_VT(port)      (&P_(port)->src.vt)
131 
132 /*================================================================
133  * Locking the ports
134  *
135  *  Since most of the public APIs locks the ports, you don't usually
136  *  need to lock the ports by yourself.   The following macros
137  *  shouldn't be used casually.
138  *
139  *  Port locking overhead is critical to the I/O performance.
140  *  The following macros are designed carefully so that it minimizes
141  *  the call to the system-level lock primitives, under the assumption
142  *  that port access never conflicts in the performance critical code.
143  *  (It doesn't make much sense for multiple threads to write to the
144  *  same port, since the outputs are mixed in unpredictable way---except
145  *  a casual debug print to stderr, but I don't believe performance
146  *  critical part does that.)
147  *
148  *  The port's lock state is kept in a single pointer, port->lockOwner.
149  *  It points to the owner of the port, or NULL if the port is unlocked.
150  *  Unlocking the port is a single atomic operation, port->lockOwner = NULL,
151  *  hence PORT_UNLOCK doesn't need mutex to do that.
152  *
153  *  To lock the port, the thread needs to grab a system-level lock
154  *  (spinlock if available, mutex otherwise) to check the lockOwner
155  *  pointer.  If the port is locked, the thread yields CPU and
156  *  try again later.
157  *
158  *  It is possible that lockOwner slot changes its value to NULL during
159  *  a thread is trying to lock the port, since PORT_UNLOCK doesn't obtain
160  *  the system-level lock.  If it happens, the thread trying to lock
161  *  the port would wait extra timeslice.  Not a big deal.
162  *
163  *  Note that we cannot use a condition variable to let the locking thread
164  *  wait on it.  If we use CV, unlocking becomes two-step operation
165  *  (set lockOwner to NULL, and call cond_signal), so it is no longer
166  *  atomic.  We would need to get system-level lock in PORT_UNLOCK as well.
167  */
168 
169 /* Lock a port P.  Can perform recursive lock. */
170 #define PORT_LOCK(p, vm)                                        \
171     do {                                                        \
172         if (P_(p)->lockOwner != vm) {                           \
173           for (;;) {                                            \
174               ScmVM* owner__;                                   \
175               (void)SCM_INTERNAL_FASTLOCK_LOCK(P_(p)->lock);    \
176               owner__ = P_(p)->lockOwner;                       \
177               if (owner__ == NULL                               \
178                   || (owner__->state == SCM_VM_TERMINATED)) {   \
179                   P_(p)->lockOwner = vm;                        \
180                   P_(p)->lockCount = 1;                         \
181               }                                                 \
182               (void)SCM_INTERNAL_FASTLOCK_UNLOCK(P_(p)->lock);  \
183               if (P_(p)->lockOwner == vm) break;                \
184               Scm_YieldCPU();                                   \
185           }                                                     \
186       } else {                                                  \
187             P_(p)->lockCount++;                                 \
188       }                                                         \
189     } while (0)
190 
191 /* Unlock a port P.  Assumes the calling thread has the lock */
192 #define PORT_UNLOCK(p)                                  \
193     do {                                                \
194         if (--P_(p)->lockCount <= 0) {                  \
195             SCM_INTERNAL_SYNC();                        \
196             P_(p)->lockOwner = NULL;                    \
197         } \
198     } while (0)
199 
200 /* Should be used while P is locked by calling thread.
201    Evaluate C statement CALL, making sure the port is unlocked in case
202    CALL raises an error.
203    CLEANUP is a C stmt called no matter CALL succeeds or not.
204    TODO: we may be able to utilize SCM_PORT_PRIVATE flag to avoid
205    SCM_UNWIND_PROTECT overhead. */
206 #define PORT_SAFE_CALL(p, call, cleanup)        \
207     do {                                        \
208        SCM_UNWIND_PROTECT {                     \
209            call;                                \
210            cleanup;                             \
211        } SCM_WHEN_ERROR {                       \
212            cleanup;                             \
213            PORT_UNLOCK(p);                      \
214            SCM_NEXT_HANDLER;                    \
215        } SCM_END_PROTECT;                       \
216     } while (0)
217 
218 #define PORT_LOCKED(p, vm) ((P_(p)->lockOwner == (vm)))
219 
220 /* Should be used in the constructor of private ports.
221    Mark the port locked by vm, so that it can be used exclusively by
222    the vm. */
223 
224 #define PORT_PRELOCK(p, vm)                     \
225    do {                                         \
226        P_(p)->lockOwner = vm;                   \
227        P_(p)->lockCount = 1;                    \
228    } while (0)
229 
230 
231 #endif /*GAUCHE_PRIV_PORTP_H*/
232