-
Notifications
You must be signed in to change notification settings - Fork 11
/
Design_opcodes.txt
812 lines (598 loc) · 31 KB
/
Design_opcodes.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
# Type hierarchy
Types are separated into "fundamental" and "user-defined". User-defined types
are built by combining fundamental types. A third category is "foreign types"
which are written in the implementation language of the VM and are injected by
foreign modules.
--------------------------------------------------------------------------------
# Fundamental types
Operations manipulate values. Values may represent different types. The ISA
defines a set of fundamental types, built-in into the VM itself.
These fundamental types are further divided into simple, and compound types.
The simple types are:
- byte: an 8-bit unsigned integer representing raw data
- bool: a boolean value representing either true (1) or false (0), all other
values may be implicitly converted to a boolean value
- signed integer: A two's-complement signed integer, as wide as a register.
- unsigned integer: An unsigned integer, as wide as a register.
- float: A single-precision floating-point number (32 bit).
- double: A double-precision floating-point number (64 bit).
- bit vector: A sequence of bits, with user-defined width. Bit vectors allow
construction of such values as 8-bit or 16-bit integers or bit masks.
- atom: A value representing itself.
- pointer: An opaque value providing a reference to another value. Pointers are
checked for validity before use.
Bytes (byte) are used as raw components of other data types, and of encoded
messages. They have numeric value, but are NOT used in arithmetical operations.
Booleans (bool) are used as true-false values.
Signed (iX) and unsigned (uX) integers are used to store numerical values and
are used in arithmetic expressions. Signed overflow traps by default, unsigned
wraps.
Unsigned integers are used to represent indices and offsets.
Arithmetic operations are always perfomed on full register-width integers. If
math on fixed-width integers is required bit vectors should be used.
Bit vectors are used to represent bit masks, and for arbitrary-width arithmetic.
Integers, both signed and unsigned, can be converted to bit vectors for bit
manipulation. Bit vectors at most 64 bits wide can be converted to unsigned
integers.
Signedness of basic arithmetic operation depends on type of its inputs. Type of
the result is the same as type of the left-hand operand's, and the right-hand
operand is automatically converted to match type of the left-hand operand's.
Basic arithmetic operations are: add, sub, mul, div, mod.
The compound types are:
- array: A variable-length array of values, mapping unsigned integer indexes
into values.
- struct: A collection of fields of different types, mapping atom keys into
field values.
Register width in current version of the VM is 64 bits.
------------------------------------------------------------
## Manipulation
Values of fundamental types are manipulated using VM instructions. Some
instructions are specific to a certain type (or set of types), but some are
generic ie, moving, copying, and deleting values.
### Moving
Moving a value between registers is a simple operation and invisible to values.
### Copying
Copying a value between registers requires knowledge of the type's internals.
Creating a copy is always deep ie, a copy is a full clone of an object and, in
case of compound types, all its subobjects.
### Deleting
Deleting a value causes its memory to be freed. If a user-defined type requires
additional actions to be performed before being deleted responsibility for
invoking it lies with the user.
--------------------------------------------------------------------------------
# Foreign types
Foreign types are written in the implementation language of the VM and
manipulated using the FFI infrastructure.
Foreign types MUST provide suitable overloads for copying and deletion.
Foreign types SHOULD provide overloads for comparison, unless such operations do
not make sense for them.
If such a type intends to be used as a substitute for a fundamental type (eg, an
integer with additional properties) it MUST implement all interfaces implemented
by the fundamental type it substitutes.
================================================================================
# Registers
There are at most N general purpose registers. Every function is responsible
for allocating them itself using the "allocate_registers" instruction.
General purpose register 0 is defined as the return-value register.
There are also at most N function call registers used to pass arguments to a
function. These are allocated using the "frame" instruction.
In case of object-oriented code, argument register 0 is defined as the
this-object register.
The N is defined to be 2^8.
## Register access
0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| set |mod| res | index |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Register sets (3 bits) as "set":
- 000: void (ignored or defaulted value)
- 001: local (general purpose)
- 010: arguments (write-only from caller)
- 011: parameters (read-only from callee)
- 1__: reserved
Access mode (2 bits) as "mod":
- 00: direct
- 01: pointer-dereference
- 1_: reserved
Reserved (3 bits) as "res".
Index (8 bits) as "index".
Written in code as:
; direct access to general purpose register 42
delete %42
; pointer-dereference of general purpose register 42 in the input field
bitnot %1, *42
; pass-by-move from general purpose register 42 into frame register 0
move %frame(0), %42
--------------------------------------------------------------------------------
# Registers (alternative)
There are several register sets available:
- void: for ignored (of dest) or defaulted (of source) values
- local: for local variables of a function
- static: for static variables of a function
- parameters: which stores the parameters the function expected to receive from
the caller
- arguments: which stores the arguments made for the callee
000 void
001 local
010 static
100 parameters
101 arguments
There are 5 register sets, so we need 3 bits to represent them all.
> MAYBE "static" register set should be scrapped? It can be replaced by spawning
> an actor which will accumulate and preserve values, and replacing direct calls
> with message exchanges.
>
> This would reduce the number of different register sets to 4 and save 1 bit or
> representation.
There are two modes in which the register may be used:
- direct: when the value is available directly from the register
- pointer-dereference: when the value is available through a pointer stored in
the register
There are 2 modes, so we need 1 bit to represent them.
Apart from register set and mode, a register access spec also contains the
register index. The register index is encoded on 8 bits.
The total width of an encoded register access is 12 bits.
================================================================================
# Instruction encoding
Instructions are encoded using 64-bit wide units.
There are several kinds of instruction encodings, but they all follow a common
theme: the lowest 16 bits are the opcode, and the rest (48 bits) are used to
encode operands and flags.
## 3-way register access kind (T format)
eg, add, bitshl, eq
- 16 bits opcode
- 12 bits output register
- 4 bits padding
- 12 bits "left-hand" input register
- 4 bits padding
- 12 bits "right-hand" input register
- 4 bits padding
## 3-way register access with flags kind (A format)
eg, aadd (Arithmetic Add)
- 16 bits opcode
- 12 bits output register
- 4 bits lowest nibble of flags (integer width specifier (IWS))
- 12 bits "left-hand" input register
- 4 bits middle nibble of flags
- 12 bits "right-hand" input register
- 4 bits highest nibble of flags (behaviour specifier (BS))
The "Arithmetic X" instructions implement controlled arithmetic on integers of a
few predefined widths: 8 (0000), 16 (0001), 32 (0010), and 64 (0011) bits. The
result produced by an operation is verified against the desired width, and is
corrected if it does not conform to it.
The behaviour specifier determines the action taken to "correct" the result, and
is one of: wrap-around, trap, or saturate.
For example, the instruction to perform a 16-bit saturating multiplication is:
amul16.s[atrurate] %o, %l, %r
The instruction to perform 32-bit trapping addition is:
aadd32.t[rap] %o, %l, %r
## 2-way register access kind (D format)
eg, bitswidth, bitnot, conversion instructions, copy, exception, exception_tag
- 16 bits opcode
- 12 bits output register
- 4 bits padding
- 12 bits input register
- 4 bits padding
- 16 bits overhead
## 1-way register access kind (S format)
eg, delete, self, throw, struct, buffer
- 16 bits opcode
- 12 bits output register
- 4 bits padding
- 32 bits overhead
## 1-way register access, 32-bit wide immediate (F format)
eg, float
eg, muw (Mask Upper Word), mlw (Mask Lower Word)
- 16 bits opcode
- 12 bits output register
- 4 bits padding
- 32 bits of immediate value
## 1-way register access, 36-bit wide immediate (E format)
eg, atom, function, closure, catch (for string table offset)
eg, lui
- 16 bits opcode
- 12 bits output register
- 36 bits of immediate value
Offset calculation: (offset * 8)
Strings in strings table are aligned on 8 bytes.
## 2-way register access, 24-bit wide immediate (R format)
eg, addi
- 16 bits opcode
- 12 bits output register
- 4 high nibble of highest byte of immediate
- 12 bits output register
- 4 low nibble of highest byte of immediate
- 16 bits lowest of immediate value
HOW TO construct a 64-bit unsigned integer?
li %i, 0xdeadbeefdeadbeef
...expands to:
; let x = 0xdeadbeefdeadbeef
g.lui %i, hi(x) ; highest 36 bits: Greedy Load Upper Immediate
g.addiu %j, void, ((lo(x) - (lo(x) % 16)) / 16)
g.addiu %k, void, 16
g.mul %j, %j, %k
g.addiu %k, void, (lo(x) % 16)
g.add %j, %j, %k
add %i, %i, %j
HOW TO construct a 64-bit signed integer?
Load it as an unsigned and use signed instructions during arithmetic.
## no operands
eg, nop, return
- 16 bits opcode
- 48 bits overhead
================================================================================
# No operation, no operands
These instructions do not do anything and may act as placeholders or
padding.
- nop No operation. May be used as padding or placeholder
instruction if needed.
- ebreak Environment break. May be used by debuggers.
- halt Halts the processor.
--------------------------------------------------------------------------------
# Register-size integer arithmetic
These instructions operate on register-bits wide integers. Their operands are
raw integers, but each instruction may choose to interpret them as signed or
unsigned.
- li %rx, <val> Constructs an integer with value <val> in register rx.
The value is sign-extended to match register width.
Values too big to be stored cause a compilation error.
- add %ro, %rl, %rr Adds integer in %rl to integer in register %rr and
stores the result in register %ro. If the result exceeds
the range of possible values stored in a register the
result is either wrapped-around (default), saturated, or
traps, depending on the ARITHMETIC_OVERFLOW processor
setting.
- sub %ro, %rl, %rr ditto, subtraction
- mul %ro, %rl, %rr ditto, multiplication
- div %ro, %rl, %rr ditto, division
- mod %ro, %rl, %rr ditto, division remainder
- addi %ro, <val> Adds an immediate <val> to integer in register %rs and
stores the result in register %ro. <val> MUST be at most
28 bits, and is sign-extended before being added to the
value in %rs.
- subi %ro, <val> ditto, subtraction
- muli %ro, <val> ditto, multiplication
- divi %ro, <val> ditto, division
- modi %ro, <val> ditto, division remainder
- addiu %ro, %ri, <val> Adds an unsigned immediate <val> to unsigned integer in
register %ri and stores the result in register %ro. The
immediate is at most 28 bits wide.
FIXME What about unsigned values?
--------------------------------------------------------------------------------
# Defined-width integer arithmetic
These instructions operate on defined-width integers. Their input operands may
be of different widths, but their outputs inherit the width of the right-hand
side operand (default), or are extended to accommodate the results value
depending on the BIT_ARITHMETIC_OVERFLOW processor setting.
- bits %ro, %rs Create a defined-width bit vector in register %ro. The
width is specified by an unsigned integer in register
%rs.
- bitsofi %ro, %rs Convert an integer in register %rs to a bit vector and
store the result in register %ro. Width of the resulting
bit vector is equal to register width.
- bitsi %ro, <val> Create a defined-width bit vector in register %ro. The
result is the shortest possible bit vector that can
store the desired value. The width is rounded-up to a
full nibble, and the result is sign-extended if the
rounding occured.
- bitswidth %rw, %rb Check the size of bit vector in register %rb. Store the
result in register %rw.
- bitadd %ro, %rl, %rr Adds bit vector value in register %rr to bit vector
value in register %rl, and stores the result in register
%ro. If the result exceeds the range of left-hand side
operand the result is either wrapped-around (default),
saturated, traps, or is extended depending on the
BIT_ARITHMETIC_OVERFLOW processor setting.
- bitsub %ro, %rl, %rr ditto, subtraction
- bitmul %ro, %rl, %rr ditto, multiplication
- bitdiv %ro, %rl, %rr ditto, division
- bitmod %ro, %rl, %rr ditto, division remainder
The following code:
bitsi %1, 0xdead
bitsi %2, 0xbeef
bitadd.wrap %1, %1, %2
...implements fixed-widdth wrapping arithmetic. The other suffixes possible for
bit arithmetic are:
- ".trap" for signed, checked arithmetic
- ".utrap" for unsigned, checked arithmetic
- ".saturate" for signed, saturating arithmetic
- ".usaturate" for unsigned, saturating arithmetic
--------------------------------------------------------------------------------
# Bit operations
Bit operations operate on bit vectors, not integers. However, integer values in
input registers are transparently converted into bit vectors by bit manipulation
instructions. No such conversion takes place on output, so even if the input is
an integer the output will be a bit vector.
- bitshl %rr, %rv, %ro Shift the bit vector in register %rv left by offset
specified by unsigned integer in register %ro. Store the
result in register %rr. Zeroes are shifted in on the
right.
- bitshr %rr, %rv, %ro ditto, but shift right
- bitashr %rr, %rv, %ro Performm an arithmetic right shift of the bit vector in
register %rv by offset specified by unsigned integer in
register %ro. Store the result in register %rr. The bit
vector is sign-extended.
- bitrol %rr, %rv, %ro Rotate the bit vector in register %rv left by offset
specified by unsigned integer in register %ro. Store
result in register %rr.
- bitror %rr, %rv, %ro ditto, but rotate right
- bitand %ro, %rl, %rr Perform a bitwise-and of bit vectors in in registers %rr
and %rl as if (rl & rr). Store the result in register
%ro.
If the widths of bit vectors in registers %rr and %rl
are not the same, the result inherits the width of the
left-hand bit vector. If the right-hand side bit vector
is shorter it is zero-extended; otherwise it is clipped.
- bitor %ro, %rl, %rr ditto, but bitwise-or
- bitxor %ro, %rl, %rr ditto, but bitwise-exclusive-or
- bitnot %ro, %ri Produce a bitwise negation of the bit vector in register
%ri and store the result in register %ro.
- bitcut %rt, %rb, %re Extract a slice of the bit vector %rt and store the
result in the same register. The slice contains bits
from index B and the next E, more significant,
positions, so from [B, B+E].
B is an unsigned integer loaded from register %rb. E is
an unsigned integer loaded from register %re.
If %rb is void, B is 0.
If %re is void, E is set to cut to the end of vector.
The following code will extract bits [4,11] from a 16-bit vector:
bits %1, %0 parameters
li %2, 4
li %3, 8
bitcut %1, %2, %3
--------------------------------------------------------------------------------
# Logic operations
- eq %ro, %rl, %rr Compare values in registers %rl and %rr for equality. In
case of buffer values compare element values. In case of
struct values throw an exception. In case of values of
foreign types call
viuavm::traits::operators::Eq
- lt %ro, %rl, %rr ditto, but less-than relation. In case of values of
foreign types call
viuavm::traits::operators::Lt
- gt %ro, %rl, %rr ditto, but greater-than relation. In case of values of
foreign types call
viuavm::traits::operators::Gt
- cmp %ro, %rl, %rr ditto, but using generalised comparison. In case of
values of foreign types call
viuavm::traits::operators::Cmp
--------------------------------------------------------------------------------
# Floating-point arithmetic
These instructions operate on floating point values.
- float %rx, <val> Constructs a floating point value <val> in register %rx.
The value is a single-precision float (ie, C float).
- double %rx, <val> Constructs a floating point value <val> in register %rx.
The value is a double-precision float (ie, C double).
The `add`, `sub`, `mul`, and `div` opcodes are reused for floating point values
and retain their meaning. Only the types they consume and produce change.
add: viua::traits::operators::Plus
sub: viua::traits::operators::Minus
mul: viua::traits::operators::Star
div: viua::traits::operators::Slash
/*
* Example implementation of the "add" opcode.
*/
using Op = viua::traits::operators::Plus;
auto lhs = dynamic_cast<Op*>(lhr.get());
auto result = lhs(rhr.get());
--------------------------------------------------------------------------------
# Operations on atoms
There are only two useful operations on atoms themselves: creating them and
comparing them for equality (using the overloaded "eq" instruction).
- atom %ra, <val> Store atom <val> in register %ra.
Since atoms may represent symbolic names for things, they are also used for
struct field access operations.
--------------------------------------------------------------------------------
# Generic register operations
Copies, moves, deletes.
- copy %ro, %ri Copy a value from register %ro, to register %ri.
- move %ro, %ri Move a value from register %ro, to register %ri.
- swap %ro, %ri Swap values between registers %ro and %ri.
--------------------------------------------------------------------------------
# References
References make it possible to pass values without copying, and add a degree of
indirection to other places in the ISA. There are two main instructions for
this:
- ref %rp, %rs Create a reference to a value located in register %rs
and put the reference in register %rp.
- reflive %rl, %rp Check if the reference in register %rp is live, and
return the result in register %rl.
--------------------------------------------------------------------------------
# Structure operations
Allocation, field access.
- struct %rx Store an empty struct in register %rx.
- struct_at %rr, %rs, %rf
Get a reference to the value of a field in the struct in
register %rs. The name of the field is specified by the
atom in register %rf. The reference is stored in
register %rr.
- struct_insert %rs, %rf, %rv
Set value of a field in the struct in register %rr. The
name of the field is specified by the atom in register
%rf. The value of the field is moved from register %rv.
- struct_remove %rv, %rs, %rf
Remove a field from the struct in register %rr. The
name of the field is specified by the atom in register
%rf. The value of the field is moved to register %rv, or
dropped if the register is given as void.
--------------------------------------------------------------------------------
# Buffer operations
Buffers are variable-length arrays used as the basic type for holding many
values (usually of the same type).
- buffer %rb Create an buffer in register %rb.
- buffer_push %rb, %rv Push the value from register %rv to the end of the
buffer in register %rb. The value is moved.
- buffer_insert %rb, %rv, %ri
Insert the value from register %rv into the buffer in
register %rb at index specified by the integer in
register %ri. The integer is unsigned. The value is
moved. If %ri is void the value is inserted at the
beginning of the buffer.
- buffer_at %rp, %rb, %ri
Get a reference to the value of an index in the buffer
in register %rb. The index is specified by the integer
in register %ri. The reference is stored in register
%rp.
- buffer_pop %rv, %rb, %ri
Remove a value from the buffer in register %rb. The
value is removed from the index specified by the integer
in register %ri. If %ri is void, the value is removed
from the end of the buffer. The value is stored in
register %rv, or dropped if it is void.
- buffer_size %rs, %rb Obtain the size (ie, number of elements) of the buffer
in register %rb and store the unsigned integer
representing it in register %rs.
--------------------------------------------------------------------------------
# Function calls and closures
Synchronous call, tail call, actor call.
- function %rf, <fn> Create a function value representing function <fn> from.
Store the function in register %rf.
- frame %ra Create a call frame. The number of argument registers is
specified directly in the operand if the direct access
mode is used; or indirectly if the pointer-dereference
access mode is used.
- call %rr, %rf Call the function from register %rf and store the result
of the call in register %rr.
- tailcall %rf Call the function from register %rf replacing the
current call frame.
- defer %rf Defer the call to function from register %rf until the
current stack frame is removed from the stack.
Closures.
- closure %rc, <fn> Create a closure value from closure <fn> and store it in
register %rc.
- closure_copy %rc, %ri, %ro
Copy the value in register %ro into register %ri in the
closure in register %rc.
- closure_move %rc, %ri, %ro
Move the value in register %ro into register %ri in the
closure in register %rc.
--------------------------------------------------------------------------------
# Actor management and communication
Message sends and receives. Actor rendez-vous.
- actor %rp, <fn>, %rf Call the function in a new actor, and store PID of the
actor in register %rp. Any additional flags are supplied
in the register %rf (if not void).
- actor_link %rold, %rp, %rnew
Instruct the VM to deliver signals to calling actor when
status of the actor described by PID in register %rp
changes. If %rold is not void, store previous settings
in this register. Current link settings are taken from
register %rnew, unless %rnew is void.
It is illegal for both %rold and %rnew to be void.
- actor_join %rv, %rp, %rt
Wait until the process with PID specified in register
%rp exits, and store the result of its work in register
%rv. If %rt is not void it specifies the register which
contains the timeout to use.
- self %rp Store the PID of the current process in register %rp.
- send %rp, %rv Send a message to the actor with PID in register %rp.
The message to be sent is a copy of the value in
register %rv.
- receive %rv, %rt Receive a message from the mailbox and put it in
register %rv. If no message is available wait until the
timeout in register %rt passes; the time to wait is
infinite if the %rt operands is void.
--------------------------------------------------------------------------------
# I/O requests
The I/O in Viua VM is asynchronous - the submission of a request to perform an
I/O operation and fetching its result are separated. Synchronous I/O is built on
top of asynchronous primitives. The cycle of I/O is separated into three main
stages: submission, completion, and awaiting. Submission and awaiting are
performed by the user code, while completion is performed by the VM.
Submission is non-blocking. A program prepares and submits an I/O request to the
VM, which consumes the request and immediately returns a proxy allowing the user
code to inquire about the status of the in-flight I/O request and, once the
requires is completed, retrieve its result.
The basic code is presented below. An I/O request is prepared and stored in
register %rr. It is submitted to the VM's I/O subsystem to be carried out
through an I/O port in register %rp. The in-flight operation's descriptor is
stored in register %rd.
io_submit %rd, %rp, %ro
Awaiting is non-blocking by default, but can be made to block a calling process
until a certain timeout passes. The in-flight operation's descriptor is stored
in register %rd, the timeout in register %rt, and the result is produced in
register %rs.
io_wait %rs, %rd, %rt
The io_wait instruction retrieves an I/O operation's result or produces an
exception if it is unable to do so ie, if the I/O operation in question has not
completed in time.
To inquire about the status of the I/O operation but not retrieve its result the
io_peek instruction is used:
io_peek %rs, %rd
Waiting for state changes a la epoll(7) is not needed because to wait for
readability it is sufficient to supply a read request and io_wait for its
completion -- no special instruction is needed.
The I/O request can be in one of the following states:
- in-flight: the request is submitted and is awaiting execution
- executing: the request is currently being executed
- finished: the request has completed successfully
- errored: the request has completed unsuccessfully
- cancelled: the request has completed without attempting execution
An I/O request can be cancelled while it's in-flight. Once it gets to an
executing or one of the completed states it's no longer possible to cancel the
request. This is inherently racy so cancelling should be used sparingly and with
extreme caution on the user side. On the VM's side, however, cancelling is
useful error-signalling mechanism.
The I/O port mentioned earlier is a structure that is able to execute I/O
requests. It may be a proxy for a physical hardware or some kind of a software
device eg, another process, or a database.
The port is constructed and destroyed in a port-specific manner, as different
devices have different needs. What is common to all ports, though, is that they
may be shut down. Even if the logic is port-specific the act of shutting down a
port MUST appear somewhere in the program.
A port should be shut down implicitly by its destructor, but if destruction is
required earlier the io_shutdown instruction should be used to trigger the shut
down:
io_shutdown %rd, %rp, void
io_shutdown %rd, %rp, %ro
If the third operand is void the port should be shut down according to the
default behaviour, but it may be acceptable for a port to receive a value
specifying how it should be shut down. For example -- sockets may shut down
either their read end, write end, or both.
Shutting down is also asynchronous and returns a descriptor of the shutdown
operation. It may be awaited using the usual methods.
The last, but not least, I/O instruction is the io_ctl. It allows tweaking
various aspects of ports and descriptors, and its behaviour is entirely defined
by the objects it acts on. It only acts as a proxy through which this
configuration is done:
io_ctl %rv, %rp, %rc
In the example above, register %rc contains some configuration, %rp contains an
I/O port, and %rv contains the value produced as a response from the port. The
%rc MUST never be void, but the %rv may be void after the io_ctl instruction is
retired.
================================================================================
HOW TO...
Instruction sequences used to perform typical tasks.
----------------------------------------
HOW TO construct a 64-bit unsigned integer?
li %i, 0xdeadbeefdeadbeef
...expands to:
; let x = 0xdeadbeefdeadbeef
g.lui %i, hi(x) ; highest 36 bits: Greedy Load Upper Immediate
g.addiu %j, void, ((lo(x) - (lo(x) % 16)) / 16)
g.addiu %k, void, 16
g.mul %j, %j, %k
g.addiu %k, void, (lo(x) % 16)
g.add %j, %j, %k
add %i, %i, %j
----------------------------------------
HOW TO construct N-bit integer?
i8:
addi %i, x
i16:
addi %i, x
i32:
g.addi %j, void, ((lo(x) - (lo(x) % 16)) / 16)
g.addi %k, void, 16
g.mul %j, %j, %k
addi %k, void, (lo(x) % 16)
u8:
addiu %i, x
u16:
addiu %i, x
u32:
g.addiu %j, void, ((lo(x) - (lo(x) % 16)) / 16)
g.addiu %k, void, 16
g.mul %j, %j, %k
addiu %k, void, (lo(x) % 16)