openMSX
MSXCPU.cc
Go to the documentation of this file.
1#include "MSXCPU.hh"
2
3#include "CPUCore.hh"
4#include "MSXCPUInterface.hh"
5#include "R800.hh"
6#include "Z80.hh"
7
8#include "MSXMotherBoard.hh"
9#include "Debugger.hh"
10#include "Scheduler.hh"
11#include "IntegerSetting.hh"
12#include "TclObject.hh"
13#include "serialize.hh"
14
15#include "outer.hh"
16#include "ranges.hh"
17#include "unreachable.hh"
18#include "xrange.hh"
19
20#include <cassert>
21#include <memory>
22
23namespace openmsx {
24
26 : motherboard(motherboard_)
27 , traceSetting(
28 motherboard.getCommandController(), "cputrace",
29 "CPU tracing on/off", false, Setting::Save::NO)
30 , diHaltCallback(
31 motherboard.getCommandController(), "di_halt_callback",
32 "Tcl proc called when the CPU executed a DI/HALT sequence",
33 "default_di_halt_callback",
34 Setting::Save::YES) // user must be able to override
35 , z80(std::make_unique<CPUCore<Z80TYPE>>(
36 motherboard, "z80", traceSetting,
37 diHaltCallback, EmuTime::zero()))
38 , r800(motherboard.isTurboR()
39 ? std::make_unique<CPUCore<R800TYPE>>(
40 motherboard, "r800", traceSetting,
41 diHaltCallback, EmuTime::zero())
42 : nullptr)
43 , timeInfo(motherboard.getMachineInfoCommand())
44 , z80FreqInfo(motherboard.getMachineInfoCommand(), "z80_freq", *z80)
45 , r800FreqInfo(r800
46 ? std::make_unique<CPUFreqInfoTopic>(
47 motherboard.getMachineInfoCommand(), "r800_freq", *r800)
48 : nullptr)
49 , debuggable(motherboard_)
50{
51 motherboard.getDebugger().setCPU(this);
52 motherboard.getScheduler().setCPU(this);
53 traceSetting.attach(*this);
54
55 z80->freqLocked.attach(*this);
56 z80->freqValue.attach(*this);
57 if (r800) {
58 r800->freqLocked.attach(*this);
59 r800->freqValue.attach(*this);
60 }
61 invalidateMemCacheSlot();
62}
63
65{
66 traceSetting.detach(*this);
67 z80->freqLocked.detach(*this);
68 z80->freqValue.detach(*this);
69 if (r800) {
70 r800->freqLocked.detach(*this);
71 r800->freqValue.detach(*this);
72 }
73 motherboard.getScheduler().setCPU(nullptr);
74 motherboard.getDebugger() .setCPU(nullptr);
75}
76
78{
79 interface = interface_;
80 z80 ->setInterface(interface);
81 if (r800) r800->setInterface(interface);
82}
83
84void MSXCPU::doReset(EmuTime::param time)
85{
86 z80 ->doReset(time);
87 if (r800) r800->doReset(time);
88
89 invalidateAllSlotsRWCache(0x0000, 0x10000);
90
91 reference = time;
92}
93
95{
96 if (cpu == Type::R800) assert(r800);
97
98 bool tmp = cpu == Type::Z80;
99 if (tmp != z80Active) {
101 newZ80Active = tmp;
102 }
103}
104
105void MSXCPU::setDRAMmode(bool dram)
106{
107 assert(r800);
108 r800->setDRAMmode(dram);
109}
110
111void MSXCPU::execute(bool fastForward)
112{
113 if (z80Active != newZ80Active) {
114 EmuTime time = getCurrentTime();
115 z80Active = newZ80Active;
116 z80Active ? z80 ->warp(time)
117 : r800->warp(time);
118 // copy Z80/R800 cache lines
119 auto zCache = z80 ->getCacheLines();
120 auto rCache = r800->getCacheLines();
121 auto from = z80Active ? rCache : zCache;
122 auto to = z80Active ? zCache : rCache;
123 ranges::copy(from.read, to.read);
124 ranges::copy(from.write, to.write);
125 }
126 z80Active ? z80 ->execute(fastForward)
127 : r800->execute(fastForward);
128}
129
131{
132 z80Active ? z80 ->exitCPULoopSync()
133 : r800->exitCPULoopSync();
134}
136{
137 z80Active ? z80 ->exitCPULoopAsync()
138 : r800->exitCPULoopAsync();
139}
140
141EmuTime::param MSXCPU::getCurrentTime() const
142{
143 return z80Active ? z80 ->getCurrentTime()
144 : r800->getCurrentTime();
145}
146
147void MSXCPU::setNextSyncPoint(EmuTime::param time)
148{
149 z80Active ? z80 ->setNextSyncPoint(time)
150 : r800->setNextSyncPoint(time);
151}
152
153void MSXCPU::invalidateMemCacheSlot()
154{
155 ranges::fill(slots, 0);
156
157 // nullptr: means not a valid entry and not yet attempted to fill this entry
158 for (auto i : xrange(16)) {
159 ranges::fill(slotReadLines[i], nullptr);
160 ranges::fill(slotWriteLines[i], nullptr);
161 }
162}
163
164void MSXCPU::updateVisiblePage(byte page, byte primarySlot, byte secondarySlot)
165{
166 assert(primarySlot < 4);
167 assert(secondarySlot < 4);
168
169 byte from = slots[page];
170 byte to = narrow<byte>(4 * primarySlot + secondarySlot);
171 slots[page] = to;
172
173 auto [cpuReadLines, cpuWriteLines] = z80Active ? z80->getCacheLines() : r800->getCacheLines();
174
175 unsigned first = page * (0x4000 / CacheLine::SIZE);
176 unsigned num = 0x4000 / CacheLine::SIZE;
177 std::copy_n(&cpuReadLines [first], num, &slotReadLines [from][first]);
178 std::copy_n(&slotReadLines [to][first], num, &cpuReadLines [first]);
179 std::copy_n(&cpuWriteLines [first], num, &slotWriteLines[from][first]);
180 std::copy_n(&slotWriteLines[to][first], num, &cpuWriteLines [first]);
181
182 if (r800) r800->updateVisiblePage(page, primarySlot, secondarySlot);
183}
184
185void MSXCPU::invalidateAllSlotsRWCache(word start, unsigned size)
186{
187 if (interface) interface->tick(CacheLineCounters::InvalidateAllSlots);
188 auto [cpuReadLines, cpuWriteLines] = z80Active ? z80->getCacheLines() : r800->getCacheLines();
189
190 unsigned first = start / CacheLine::SIZE;
191 unsigned num = (size + CacheLine::SIZE - 1) / CacheLine::SIZE;
192 ranges::fill(subspan(cpuReadLines, first, num), nullptr); // nullptr: means not a valid entry and not
193 ranges::fill(subspan(cpuWriteLines, first, num), nullptr); // yet attempted to fill this entry
194
195 for (auto i : xrange(16)) {
196 ranges::fill(subspan(slotReadLines [i], first, num), nullptr);
197 ranges::fill(subspan(slotWriteLines[i], first, num), nullptr);
198 }
199}
200
201template<bool READ, bool WRITE, bool SUB_START>
202void MSXCPU::setRWCache(unsigned start, unsigned size, const byte* rData, byte* wData, int ps, int ss,
203 std::span<const byte, 256> disallowRead,
204 std::span<const byte, 256> disallowWrite)
205{
206 if constexpr (!SUB_START) {
207 assert(rData == nullptr);
208 assert(wData == nullptr);
209 }
210
211 // aligned on cache lines
212 assert((start & CacheLine::LOW) == 0);
213 assert((size & CacheLine::LOW) == 0);
214
215 int slot = 4 * ps + ss;
216 unsigned page = start >> 14;
217 assert(((start + size - 1) >> 14) == page); // all in same page
218 if constexpr (SUB_START && READ) rData -= start;
219 if constexpr (SUB_START && WRITE) wData -= start;
220
221 // select between 'active' or 'shadow' cache lines
222 auto [readLines, writeLines] = [&] {
223 if (slot == slots[page]) {
224 return z80Active ? z80->getCacheLines() : r800->getCacheLines();
225 } else {
226 return CacheLines{slotReadLines [slot],
227 slotWriteLines[slot]};
228 }
229 }();
230
231 unsigned first = start / CacheLine::SIZE;
232 unsigned num = size / CacheLine::SIZE;
233
234 static auto* const NON_CACHEABLE = std::bit_cast<byte*>(uintptr_t(1));
235 for (auto i : xrange(num)) {
236 if constexpr (READ) readLines [first + i] = disallowRead [first + i] ? NON_CACHEABLE : rData;
237 if constexpr (WRITE) writeLines[first + i] = disallowWrite[first + i] ? NON_CACHEABLE : wData;
238 }
239}
240
241static constexpr void extendForAlignment(unsigned& start, unsigned& size)
242{
243 constexpr unsigned MASK = ~CacheLine::LOW; // not CacheLine::HIGH because 0x0000ff00 != 0xffffff00
244
245 auto end = start + size;
246 start &= MASK; // round down to cacheline
247 end = (end + CacheLine::SIZE - 1) & MASK; // round up to cacheline
248 size = end - start;
249}
250
251void MSXCPU::invalidateRWCache(unsigned start, unsigned size, int ps, int ss,
252 std::span<const byte, 256> disallowRead,
253 std::span<const byte, 256> disallowWrite)
254{
255 // unaligned [start, start+size) is OK for invalidate, then simply invalidate a little more.
256 extendForAlignment(start, size);
257 setRWCache<true, true, false>(start, size, nullptr, nullptr, ps, ss, disallowRead, disallowWrite);
258}
259void MSXCPU::invalidateRCache(unsigned start, unsigned size, int ps, int ss,
260 std::span<const byte, 256> disallowRead,
261 std::span<const byte, 256> disallowWrite)
262{
263 extendForAlignment(start, size);
264 setRWCache<true, false, false>(start, size, nullptr, nullptr, ps, ss, disallowRead, disallowWrite);
265}
266void MSXCPU::invalidateWCache(unsigned start, unsigned size, int ps, int ss,
267 std::span<const byte, 256> disallowRead,
268 std::span<const byte, 256> disallowWrite)
269{
270 extendForAlignment(start, size);
271 setRWCache<false, true, false>(start, size, nullptr, nullptr, ps, ss, disallowRead, disallowWrite);
272}
273
274void MSXCPU::fillRWCache(unsigned start, unsigned size, const byte* rData, byte* wData, int ps, int ss,
275 std::span<const byte, 256> disallowRead,
276 std::span<const byte, 256> disallowWrite)
277{
278 setRWCache<true, true, true>(start, size, rData, wData, ps, ss, disallowRead, disallowWrite);
279}
280void MSXCPU::fillRCache(unsigned start, unsigned size, const byte* rData, int ps, int ss,
281 std::span<const byte, 256> disallowRead,
282 std::span<const byte, 256> disallowWrite)
283{
284 setRWCache<true, false, true>(start, size, rData, nullptr, ps, ss, disallowRead, disallowWrite);
285}
286void MSXCPU::fillWCache(unsigned start, unsigned size, byte* wData, int ps, int ss,
287 std::span<const byte, 256> disallowRead,
288 std::span<const byte, 256> disallowWrite)
289{
290 setRWCache<false, true, true>(start, size, nullptr, wData, ps, ss, disallowRead, disallowWrite);
291}
292
294{
295 z80 ->raiseIRQ();
296 if (r800) r800->raiseIRQ();
297}
299{
300 z80 ->lowerIRQ();
301 if (r800) r800->lowerIRQ();
302}
304{
305 z80 ->raiseNMI();
306 if (r800) r800->raiseNMI();
307}
309{
310 z80 ->lowerNMI();
311 if (r800) r800->lowerNMI();
312}
313
314bool MSXCPU::isM1Cycle(unsigned address) const
315{
316 return z80Active ? z80 ->isM1Cycle(address)
317 : r800->isM1Cycle(address);
318}
319
320void MSXCPU::setZ80Freq(unsigned freq)
321{
322 z80->setFreq(freq);
323}
324
325void MSXCPU::wait(EmuTime::param time)
326{
327 z80Active ? z80 ->wait(time)
328 : r800->wait(time);
329}
330
331EmuTime MSXCPU::waitCyclesZ80(EmuTime::param time, unsigned cycles)
332{
333 return z80Active ? z80 ->waitCycles(time, cycles)
334 : time;
335}
336
337EmuTime MSXCPU::waitCyclesR800(EmuTime::param time, unsigned cycles)
338{
339 return z80Active ? time
340 : r800->waitCycles(time, cycles);
341}
342
344{
345 if (z80Active) {
346 return *z80;
347 } else {
348 return *r800;
349 }
350}
351
352void MSXCPU::update(const Setting& setting) noexcept
353{
354 z80 ->update(setting);
355 if (r800) r800->update(setting);
356 exitCPULoopSync();
357}
358
359void MSXCPU::setPaused(bool paused)
360{
361 if (z80Active) {
362 z80 ->setExtHALT(paused);
363 z80 ->exitCPULoopSync();
364 } else {
365 r800->setExtHALT(paused);
366 r800->exitCPULoopSync();
367 }
368}
369
370
371// class TimeInfoTopic
372
373MSXCPU::TimeInfoTopic::TimeInfoTopic(InfoCommand& machineInfoCommand)
374 : InfoTopic(machineInfoCommand, "time")
375{
376}
377
378void MSXCPU::TimeInfoTopic::execute(
379 std::span<const TclObject> /*tokens*/, TclObject& result) const
380{
381 const auto& cpu = OUTER(MSXCPU, timeInfo);
382 EmuDuration dur = cpu.getCurrentTime() - cpu.reference;
383 result = dur.toDouble();
384}
385
386std::string MSXCPU::TimeInfoTopic::help(std::span<const TclObject> /*tokens*/) const
387{
388 return "Prints the time in seconds that the MSX is powered on\n";
389}
390
391
392// class CPUFreqInfoTopic
393
394MSXCPU::CPUFreqInfoTopic::CPUFreqInfoTopic(
395 InfoCommand& machineInfoCommand,
396 const std::string& name_, CPUClock& clock_)
397 : InfoTopic(machineInfoCommand, name_)
398 , clock(clock_)
399{
400}
401
402void MSXCPU::CPUFreqInfoTopic::execute(
403 std::span<const TclObject> /*tokens*/, TclObject& result) const
404{
405 result = clock.getFreq();
406}
407
408std::string MSXCPU::CPUFreqInfoTopic::help(std::span<const TclObject> /*tokens*/) const
409{
410 return "Returns the actual frequency of this CPU.\n"
411 "This frequency can vary because:\n"
412 " - the user has overridden the freq via the '{z80,r800}_freq' setting\n"
413 " - (only on some MSX machines) the MSX software can switch the Z80 between 2 frequencies\n"
414 "See also the '{z80,r800}_freq_locked' setting.\n";
415}
416
417
418// class Debuggable
419
420static constexpr static_string_view CPU_REGS_DESC =
421 "Registers of the active CPU (Z80 or R800).\n"
422 "Each byte in this debuggable represents one 8 bit register:\n"
423 " 0 -> A 1 -> F 2 -> B 3 -> C\n"
424 " 4 -> D 5 -> E 6 -> H 7 -> L\n"
425 " 8 -> A' 9 -> F' 10 -> B' 11 -> C'\n"
426 " 12 -> D' 13 -> E' 14 -> H' 15 -> L'\n"
427 " 16 -> IXH 17 -> IXL 18 -> IYH 19 -> IYL\n"
428 " 20 -> PCH 21 -> PCL 22 -> SPH 23 -> SPL\n"
429 " 24 -> I 25 -> R 26 -> IM 27 -> IFF1/2\n"
430 "The last position (27) contains the IFF1 and IFF2 flags in respectively\n"
431 "bit 0 and 1. Bit 2 contains 'IFF1 AND last-instruction-was-not-EI', so\n"
432 "this effectively indicates that the CPU could accept an interrupt at\n"
433 "the start of the current instruction.\n";
434
435MSXCPU::Debuggable::Debuggable(MSXMotherBoard& motherboard_)
436 : SimpleDebuggable(motherboard_, "CPU regs", CPU_REGS_DESC, 28)
437{
438}
439
440byte MSXCPU::Debuggable::read(unsigned address)
441{
442 auto& cpu = OUTER(MSXCPU, debuggable);
443 const CPURegs& regs = cpu.getRegisters();
444 switch (address) {
445 case 0: return regs.getA();
446 case 1: return regs.getF();
447 case 2: return regs.getB();
448 case 3: return regs.getC();
449 case 4: return regs.getD();
450 case 5: return regs.getE();
451 case 6: return regs.getH();
452 case 7: return regs.getL();
453 case 8: return regs.getA2();
454 case 9: return regs.getF2();
455 case 10: return regs.getB2();
456 case 11: return regs.getC2();
457 case 12: return regs.getD2();
458 case 13: return regs.getE2();
459 case 14: return regs.getH2();
460 case 15: return regs.getL2();
461 case 16: return regs.getIXh();
462 case 17: return regs.getIXl();
463 case 18: return regs.getIYh();
464 case 19: return regs.getIYl();
465 case 20: return regs.getPCh();
466 case 21: return regs.getPCl();
467 case 22: return regs.getSPh();
468 case 23: return regs.getSPl();
469 case 24: return regs.getI();
470 case 25: return regs.getR();
471 case 26: return regs.getIM();
472 case 27: return byte(1 * regs.getIFF1() +
473 2 * regs.getIFF2() +
474 4 * (regs.getIFF1() && !regs.prevWasEI()));
475 default: UNREACHABLE;
476 }
477}
478
479void MSXCPU::Debuggable::write(unsigned address, byte value)
480{
481 auto& cpu = OUTER(MSXCPU, debuggable);
482 CPURegs& regs = cpu.getRegisters();
483 switch (address) {
484 case 0: regs.setA(value); break;
485 case 1: regs.setF(value); break;
486 case 2: regs.setB(value); break;
487 case 3: regs.setC(value); break;
488 case 4: regs.setD(value); break;
489 case 5: regs.setE(value); break;
490 case 6: regs.setH(value); break;
491 case 7: regs.setL(value); break;
492 case 8: regs.setA2(value); break;
493 case 9: regs.setF2(value); break;
494 case 10: regs.setB2(value); break;
495 case 11: regs.setC2(value); break;
496 case 12: regs.setD2(value); break;
497 case 13: regs.setE2(value); break;
498 case 14: regs.setH2(value); break;
499 case 15: regs.setL2(value); break;
500 case 16: regs.setIXh(value); break;
501 case 17: regs.setIXl(value); break;
502 case 18: regs.setIYh(value); break;
503 case 19: regs.setIYl(value); break;
504 case 20: regs.setPCh(value); break;
505 case 21: regs.setPCl(value); break;
506 case 22: regs.setSPh(value); break;
507 case 23: regs.setSPl(value); break;
508 case 24: regs.setI(value); break;
509 case 25: regs.setR(value); break;
510 case 26:
511 if (value < 3) regs.setIM(value);
512 break;
513 case 27:
514 regs.setIFF1((value & 0x01) != 0);
515 regs.setIFF2((value & 0x02) != 0);
516 // can't change afterEI
517 break;
518 default:
520 }
521}
522
523// version 1: initial version
524// version 2: activeCPU,newCPU -> z80Active,newZ80Active
525template<typename Archive>
526void MSXCPU::serialize(Archive& ar, unsigned version)
527{
528 if (ar.versionAtLeast(version, 2)) {
529 ar.serialize("z80", *z80);
530 if (r800) ar.serialize("r800", *r800);
531 ar.serialize("z80Active", z80Active,
532 "newZ80Active", newZ80Active);
533 } else {
534 // backwards-compatibility
535 assert(Archive::IS_LOADER);
536
537 ar.serializeWithID("z80", *z80);
538 if (r800) ar.serializeWithID("r800", *r800);
539 CPUBase* activeCPU = nullptr;
540 CPUBase* newCPU = nullptr;
541 ar.serializePointerID("activeCPU", activeCPU);
542 ar.serializePointerID("newCPU", newCPU);
543 z80Active = activeCPU == z80.get();
544 if (newCPU) {
545 newZ80Active = newCPU == z80.get();
546 } else {
547 newZ80Active = z80Active;
548 }
549 }
550 ar.serialize("resetTime", reference);
551
552 if constexpr (Archive::IS_LOADER) {
553 invalidateMemCacheSlot();
554 invalidateAllSlotsRWCache(0x0000, 0x10000);
555 }
556}
558
559} // namespace openmsx
BaseSetting * setting
void tick(ENUM e) const
void setCPU(MSXCPU *cpu_)
Definition Debugger.hh:50
void setZ80Freq(unsigned freq)
Switch the Z80 clock freq.
Definition MSXCPU.cc:320
bool isM1Cycle(unsigned address) const
Should only be used from within a MSXDevice::readMem() method.
Definition MSXCPU.cc:314
void lowerNMI()
This methods lowers the non-maskable interrupt again.
Definition MSXCPU.cc:308
void serialize(Archive &ar, unsigned version)
Definition MSXCPU.cc:526
void invalidateAllSlotsRWCache(word start, unsigned size)
Invalidate the CPU its cache for the interval [start, start + size) For example MSXMemoryMapper and M...
Definition MSXCPU.cc:185
void fillWCache(unsigned start, unsigned size, byte *wData, int ps, int ss, std::span< const byte, 256 > disallowRead, std::span< const byte, 256 > disallowWrite)
Definition MSXCPU.cc:286
void updateVisiblePage(byte page, byte primarySlot, byte secondarySlot)
Inform CPU of bank switch.
Definition MSXCPU.cc:164
void fillRCache(unsigned start, unsigned size, const byte *rData, int ps, int ss, std::span< const byte, 256 > disallowRead, std::span< const byte, 256 > disallowWrite)
Definition MSXCPU.cc:280
CPURegs & getRegisters()
Definition MSXCPU.cc:343
void fillRWCache(unsigned start, unsigned size, const byte *rData, byte *wData, int ps, int ss, std::span< const byte, 256 > disallowRead, std::span< const byte, 256 > disallowWrite)
Fill the read and write cache lines for a specific slot with the specified value.
Definition MSXCPU.cc:274
void invalidateRCache(unsigned start, unsigned size, int ps, int ss, std::span< const byte, 256 > disallowRead, std::span< const byte, 256 > disallowWrite)
Definition MSXCPU.cc:259
EmuTime waitCyclesR800(EmuTime::param time, unsigned cycles)
Definition MSXCPU.cc:337
void exitCPULoopSync()
See CPUCore::exitCPULoopSync()
Definition MSXCPU.cc:130
void setNextSyncPoint(EmuTime::param time)
Definition MSXCPU.cc:147
EmuTime waitCyclesZ80(EmuTime::param time, unsigned cycles)
Definition MSXCPU.cc:331
void raiseIRQ()
This method raises a maskable interrupt.
Definition MSXCPU.cc:293
void raiseNMI()
This method raises a non-maskable interrupt.
Definition MSXCPU.cc:303
void wait(EmuTime::param time)
Definition MSXCPU.cc:325
void doReset(EmuTime::param time)
Reset CPU.
Definition MSXCPU.cc:84
void setActiveCPU(Type cpu)
Switch between Z80/R800.
Definition MSXCPU.cc:94
void invalidateRWCache(unsigned start, unsigned size, int ps, int ss, std::span< const byte, 256 > disallowRead, std::span< const byte, 256 > disallowWrite)
Similar to the method above, but only invalidates one specific slot.
Definition MSXCPU.cc:251
void setPaused(bool paused)
(un)pause CPU.
Definition MSXCPU.cc:359
void setInterface(MSXCPUInterface *interface)
Definition MSXCPU.cc:77
void exitCPULoopAsync()
See CPUCore::exitCPULoopAsync()
Definition MSXCPU.cc:135
void setDRAMmode(bool dram)
Sets DRAM or ROM mode (influences memory access speed for R800).
Definition MSXCPU.cc:105
void lowerIRQ()
This methods lowers the maskable interrupt again.
Definition MSXCPU.cc:298
MSXCPU(MSXMotherBoard &motherboard)
Definition MSXCPU.cc:25
void invalidateWCache(unsigned start, unsigned size, int ps, int ss, std::span< const byte, 256 > disallowRead, std::span< const byte, 256 > disallowWrite)
Definition MSXCPU.cc:266
void setCPU(MSXCPU *cpu_)
Definition Scheduler.hh:40
void detach(Observer< T > &observer)
Definition Subject.hh:60
void attach(Observer< T > &observer)
Definition Subject.hh:54
static_string_view
constexpr unsigned LOW
Definition CacheLine.hh:9
constexpr unsigned SIZE
Definition CacheLine.hh:7
This file implemented 3 utility functions:
Definition Autofire.cc:11
uint8_t byte
8 bit unsigned integer
Definition openmsx.hh:26
uint16_t word
16 bit unsigned integer
Definition openmsx.hh:29
constexpr void fill(ForwardRange &&range, const T &value)
Definition ranges.hh:315
constexpr auto copy(InputRange &&range, OutputIter out)
Definition ranges.hh:252
STL namespace.
size_t size(std::string_view utf8)
#define OUTER(type, member)
Definition outer.hh:42
constexpr auto subspan(Range &&range, size_t offset, size_t count=std::dynamic_extent)
Definition ranges.hh:481
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
#define UNREACHABLE
constexpr auto xrange(T e)
Definition xrange.hh:132
constexpr auto end(const zstring_view &x)