Operational Semantics For Rio #67
KabirSamsi
started this conversation in
Ideas
Replies: 2 comments 1 reply
-
OCaml implementation under directory 'semantics' on branch operational-semantics (also #70, draft PR) Note: WFQ needs some updating. |
Beta Was this translation helpful? Give feedback.
0 replies
-
Near the top you have the following but do you mean the following? EDIT: oh never mind, on reading further I see that you use that second field to identify the destination queue. |
Beta Was this translation helpful? Give feedback.
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Operational Semantics For Rio
Overview
We define Big-Step Operational Semantics For
push
andpop
given a program and a series of queues. For each possible policy constructor form, we will define a new stepping rule.We operate with the notion of a state tuple, defined as follows:
Packets. Later on will motivate the need to express that popping a queue may or may not return a packet (in the case that the queue is empty). To this effect, we treat packets as options – that is to say, they may be either
None
orSome pkt
.Pushing and Popping. Essentially, calling
push
orpop
(with any relevant info) on a state tuple should allow us to mutate the queues based on the program. Exactly how the mutate depends on the program, hence the need for multiple semantic rules.The nature of how queues work isn't too relevant – we assume they are equipped with the following opaque functions:
Subsequently, we can define push and pop by signature:
Semantics
Push Semantics
Assume that at enqueue-time, we have a packet with a known destination queue. We also are given a program, though its structure is irrelevant beyond ensuring a well-defined state tuple. Subsequently, we can define push:
Pop Semantics of Set-To-Stream Transformers
Helpers. We assume that queues and packets are equipped with a few opaque functions that work as follows.
We define
flush : queue -> pkt set
. This returns all packets currently enqueued in a given queue.We define a totally-ordered type t. In other words, any set of elements$\{e_1 \dots e_n\}$ such that
e1 : t ... en : t
are totally ordered.We then denote the functions$e2 \leq e1$ .
rank : pkt -> t
,slack : pkt -> t
.,weight : pkt -> t
, each of which respectively takes a packet and returns a totally-ordered ranking corresponding to some property of the packet. We also assume these functions map packets such that ifNone -> e1
andSome d -> e2
, thenWe define a higher-order function
hd : (pkt -> t ) -> queue -> pkt
:And define a higher-order function
heads : (pkt -> t) -> queue list -> pkt list
:In a sense,
heads
performs the higher-ordermap
withhd
on a list of queues.Subsequently, we can define a general popping rule
POP-GEN
(with HOFpopg
) for set-to-streams:(Where$qs[qs_i/pkt_i]$ denotes $qs$ , but with $qs_i$ having $pkt_i$ removed).
Subsequently, giving the trio of operational semantics for popping from FIFOs, EDFs and SJNs:
Helpers For Stream-To-Stream Transformers
The key with rigorously defining stream-to-stream transformers is that we need some way to figure out how to partition queues when popping, in order to lock down which partitioning of queues are involved with each recursive call.
To do so, we define
nclasses
and a series of semantics, allowing us to find the number of classes defined under a policy:We then dictate rules to partition and merge queues based on the rules of substreams such that we can have well-defined rules:
This equips us to then define semantics for stream-to-stream popping.
Pop Semantics of Stream-To-Stream Transformers
Round-Robin
Though we had previously asserted that programs will not change with a big-step, we can tweak the structure of a Round-Robin program to good effect, giving the following rule:
Essentially, we orchestrate
pop
on the first stream child, return that packet, modify the queues, and then move that stream to the end. This formalizes the 'one-per-round' round-robin effect.Strict Queueing
This is the primary case where we are concerned about whether packets are
None
orSome
. We utilize the optionality to formalize the network-y notion of a stream 'falling silent'.Strategy. We can develop two inference rules. Once dictates that if every sub-stream pops
None
, the result of popping from the current stream transformer isNone
as well. The other finds the first sub-stream for whichSome
is returned, and returns that as the popping result.In the first case, we consider the situation where every stream has fallen silent. In this case, the resultant strict stream must as well:
In the second case, we find the first non-silent stream, and return its packet and modified program/queue. It results in the slightly wordy rule:
Weighted-Fair-Queueing
WFQ Programs contain both a list of sub-streams, and a list of weights, which we type as integers.
With this in mind, we can design four big-stepping rules, each one contingent on the possible program setup.
We'll once again utilize the notion of program modification in state-to-state stepping. We'll also enforce that weights must be positive integers$\geq 1$ , and that the number of weights should be either the same length as, or one more than, the number of sub-streams.
Important Note. Round-Robin is just WFQ with all the weights set to$1$ . We'll use this notion as we define the semantics.
Strategy. With each iteration, cache the current weight by adding it to the end of the list, and then decrease the weight by one. Once the head's weight reaches 1, we pop the element and step to a state where the remaining$n$ elements of the tail form the new weights. Subsequently, after a full pass, we end up at the same state of programs and weights as initially, giving the desired effect.
Case 1. Let$a_1 = 1$ (Denoting the first weight) and let $m$ (Number of weights) $= n$ (Number of sub-streams).
The implication is then that we have just started a new round, with a weight of$1$ . We cache the head value at the end, and pop from the front. This is effectively just performing
pop
on a Round-Robin policy.Case 2. Let$a_1 > 1$ , and let $m = n$ .
The implication is then that we have just started a new round. We cache the head value at the end (thereby getting$m = n+1$ ), and subtract from the front.
Case 3. Let$a_1 = 1$ , and let $m = n+1$ .
Pop as normal, and then remove the head (thereby getting$m = n$ ). The cached value is already at the end, so will loop around.
Case 4. Let$a_1 > 1$ , and let $m = n+1$ .
All we need to do here is subtract$1$ from the head weight, and pop as normal. The cached value is already at the end.
Beta Was this translation helpful? Give feedback.
All reactions