Памятка по языку P4

PDF

Базовые типы данных

typedef

Служит для определения дополнительного имени типа.

typedef bit<48> macAddr_t; 	// Объявление нового типа для 48-битового MAC-адреса Ethernet
typedef bit<32> ip4Addr_t;	// Объявление нового типа для 32-битового адреса IPv4

header

Упорядоченный набор элементов (полей заголовка). Заголовок обязательно включает скрытый элемент (бит) validity. Для проверки и изменения этого бита служат функции isValid(), setValid(), setInvalid().

header ethernet_t {
	macAddr_t dstAddr;	// Поле заголовка с MAC-адресом получателя
	macAddr_t srcAddr;	// Поле заголовка с MAC-адресом отправителя
	bit<16> type;		// Поле заголовка, указывающее тип кадра (EtherType)
}

Объявление переменных для заголовков имеет вид

ethernet_t ethernet;		// Переменная типа ethernet_t

Для доступа к полям заголовков используется нотация с точкой, как показано ниже.

macAddr_t src = ethernet.srcAddr;	// Переменная src получает MAC-адрес отправителя

struct

Структуры в P4 представляют собой неупорядоченные наборы элементов.

struct headers_t {
	ethernet_t ethernet;	// Структура, содержащая 1 заголовок типа ethernet_t
}

Стеки заголовков

Стек заголовков представляет собой индексированный массив элементов типа header.

header label_t {	// Объявление заголовка label_t (метка).
	bit<20> label;	// Собственно метка.
	bit bos;	// Флаг последней метки в стеке.
}

struct header_t {	// Объявление стека меток.
	label_t[10] labels;	// Стек меток.
}

header_t hdr;		// Переменная стека меток.

action pop_label() {	// Удаление метки из стека.
	hdr.labels.pop_front(1); // Выталкивание метки.
}

action push_label(in bit<20> label) {	// Добавление метки в стек.
	hdr.labels.push_front(1);		// Вталкивание метки.
	hdr.labels[0].setValid();		// Объявление метки 0 действительной.
	hdr.labels[0] = {label, 0};
}

Операторы и выражения

Объявление и назначение локальных метаданных

bit<16> tmp1; bit<16> tmp2;	// Переменные tmp1 и tmp1 типа bit<16>
tmp1 = hdr.ethernet.type;	// Переменная tmp1 получает значение EtherType из заголовка пакета

«Нарезка» и объединение (конкатенация) битов

tmp2 = tmp1[7:0] ++ tmp1[15:8];	// Переменная tmp2 получает значение объединения битов 0-7
					// из переменной tmp1 с битами 8-15 из той же переменной.
					// В данном случае это перестановка старшего и младшего байтов

Сложение, вычитание и приведение типов

tmp2 = tmp1 + tmp1 — (bit<16>)tmp1[7:0];	// Переменная tmp2 получает значение суммы tmp1 и 
						// старшего байта tmp1

Побитовые операции

tmp2 = (~tmp1 & tmp1) | (tmp1 ^ tmp1);	// Переменная tmp2 получает значение 0, поскольку 
						// операции И для числа и его «отрицания», а также 
						// Исключительное ИЛИ с самим собой дают 0, а 
						// ИЛИ для двух нулей также дает 0.
tmp2 = tmp1 << 3;				// Переменная tmp2 получает значение смещенных на
						// три позиции влево битов переменной tmp1. Три 
						// младших бита будут иметь значение 0.

Действия

Операции на основании входных данных от плоскости управления.

action set_next_hop(bit<32> next_hop) {		// Установка следующего интервала пересылки
	if (next_hop == 0) {				// Если параметр next_hop еще не задан,	
		metadata.next_hop = hdr.ipv4.dst;	// в поле метаданных помещается адрес получателя
							// из заголовка IP в пакете.
	} else {					// В противном случае поле метаданных
		metadata.next_hop = next_hop;		// получает значение параметра next_hop.
	}
}

Операции на основании входных параметров от плоскости данных.

action swap_mac(inout bit<48> x,	// Перестановка MAC-адресов отправителя и получателя,
		 inout bit<48> y) {	// переданных параметрами действия.
	bit<48> tmp = x;		// Сохранение адреса x во временной переменной.
	x = y;				// Назначение переменной x значения переменной y.
	y = tmp;			// Назначение переменной y исходного значения переменной x.
}

Операции на основании входных параметров от плоскостей данных и управления.

action forward(in bit<9> p, bit<48> d) {	// Задание выходного порта и адреса получателя.
	standard_metadata.egress_spec = p;	// Указание выходного порта в метаданных по параметру.
	headers.ethernet.dstAddr = d;		// Указание адреса получателя по параметру.
}

Удаление заголовка из пакета (декапсуляция).

action decap_ip_ip() {			// Декапсуляция внешнего заголовка IP.
	hdr.ipv4 = hdr.inner_ipv4;	// Перенос внутреннего заголовка во внешний.
	hdr.inner_ipv4.setInvalid();	// Объявление внутреннего заголовка недействительным.
}

Таблицы

Ниже приведен пример определения таблицы для поска по максимальному совпадению префиксов.

table ipv4_lpm {				// Таблица сопоставления по префиксам
	key = {
		hdr.ipv4.dstAddr : lpm;	// Выполняется сопоставление lpm для поиска самого длинного
						// префикса. Стандартные типы сопоставления - exact, ternary, 
	}					// lpm.
	// Задание возможных действий.
	actions = {
		ipv4_forward;		// Пересылка IPv4.
		Drop;			// Отбрасывание.
		NoAction;		// Нет действий.
	}
	// Свойства таблицы
	size = 1024;			// Число записей
	default_action = NoAction();	// Используемое при отсутствии совпадений действие.
}

Поток управления

Метод apply служит для вызова блоков «сопоставление-действие».

apply {		// Ветвление по признаку действительности заголовка.
	if (hdr.ipv4.isValid()) {	// Если заголовок пакета IPv4 действителен,
		ipv4_lpm.apply();	// выполняется сопоставление по самому длинному префиксу.
	}
	// Ветвление по результатам поиска в таблице
	if (local_ip_table.apply().hit) {	
		send_to_cpu();		// Передача пакета в порт CPU
	}
	// Ветвление для выбора действия в таблице.
	switch (table1.apply().action_run) { 
		action1: { table2.apply(); }
		action2: { table3.apply(); }
	}
}

Синтаксический анализ заголовков

Использование внешнего элемента packet_in для входного пакета.

extern packet_in {
	void extract<T>(out T hdr);			// Извлечение заголовка в выходную переменную T.
	void extract<T>(out T hdr,in bit<32> n);	// Назначение значения выходной переменной T
							// (заголовок) по входной переменной n.
	T lookahead<T>();				// Извлечение заголовка в T.
	void advance(in bit<32> n);			// Перемещение указателя текущей позиции на n.
	bit<32> length();				// Размер пакета.	
}

Анализ начинается с предопределенного состояния start.

state start {
	transition parse_ethernet;	// Переход к анализу заголовка Ethernet.
}

Заданное пользователем состояние анализатора для определения типа пакета.

state parse_ethernet {
	packet.extract(hdr.ethernet);			// Извлечение заголовка Ethernet.
	transition select(hdr.ethernet.type) {	// Определение типа пакета.
		0x800: parse_ipv4;			// Анализ заголовка IP при EtherType=IP.
		default: accept;			// Восприятие прочих пакетов без дополнительного
	}						// анализа.
}

Определения для заголовков IPv4 и IPv6.

header ip46_t {
	bit<4> version;	// Версия протокола IP.
	bit<4> reserved;
}

Анализ стека заголовков.

state parse_labels {					// Анализ меток в заголовке
	packet.extract(hdr.labels.next);		// Извлечение метки
	transition select(hdr.labels.last.bos) {
		0: parse_labels; 			// Цикл анализа меток.
		1: guess_labels_payload;		// Последняя метка
	}
}

Анализ данных после стека заголовков.

state guess_labels_payload {				// Анализ содержимого.
	transition select(packet.lookahead<ip46_t>().version) {
		4 : parse_inner_ipv4;			// Пакет IPv4
		6 : parse_inner_ipv6;			// Пакет IPv6
		default : parse_inner_ethernet;	// Кадр Ethernet.
	}
}

Сборка пакетов после обработки

Внешний элемент packet_out используется для отправки обработанных пакетов.

extern packet_out {
	void emit<T>(in T hdr);
}
apply {		// Включение действительных заголовков в пакет
	packet.emit(hdr.ethernet);	// Отправка пакета.
}

Архитектура V1Model

Базовые внешние элементы архитектуры перечислены ниже.

extern void truncate(in bit<32> length);
extern void resubmit<T>(in T x);
extern void recirculate<T>(in T x);
enum CloneType { I2E, E2I }
extern void clone(in CloneType type, in bit<32> session);
// Элементы конвейера v1model.
parser Parser<H, M>(packet_in pkt,
		     out H hdr,
		     inout M meta,
		     inout standard_metadata_t std_meta);
control VerifyChecksum<H, M>(inout H hdr, inout M meta );
control Ingress<H, M>(inout H hdr,
			inout M meta,
			inout standard_metadata_t std_meta);
control Egress<H, M>(inout H hdr,
			inout M meta,
			inout standard_metadata_t std_meta);
control ComputeChecksum<H, M>(inout H hdr, inout M meta );
control Deparser<H>( packet_out b, in H hdr);
// Коммутатор v1model
package V1Switch<H, M>(Parser<H, M> p,
			VerifyChecksum<H, M> vr,
			Ingress<H, M> ig,
			Egress<H, M> eg,
			ComputeChecksum<H, M> ck,
			Deparser<H> d);

Стандартные метаданные V1Model

struct standard_metadata_t {
	bit<9>	ingress_port;
	bit<9>	egress_spec;
	bit<9>	egress_port;
	bit<32>	clone_spec;
	bit<32>	instance_type;
	bit<1>	drop;
	bit<16>	recirculate_port;
	bit<32>	packet_length;
	bit<32>	enq_timestamp;
	bit<19>	enq_qdepth;
	bit<32>	deq_timedelta;
	bit<19>	deq_qdepth;
	bit<48>	ingress_global_timestamp;
	bit<48>	egress_global_timestamp;
	bit<32>	lf_field_list;
	bit<16>	mcast_grp;
	bit<32>	resubmit_flag;
	bit<16>	egress_rid;
	bit<1>	checksum_error;
	bit<32>	recirculate_flag;
}

Счетчики и регистры V1Model

Счетчики

counter(8192, CounterType.packets) c;
action count(bit<32> index) {
	c.count(index);	// Инкрементирование счетчика пакетов по индексу.
}

Регистры

register<bit<48>>(16384) r;
action ipg(out bit<48> ival, bit<32> x) {
	bit<48> last;
	bit<48> now;
	r.read(last, x);
	now = std_meta.ingress_global_timestamp;
	ival = now - last;
	r.write(x, now);
}

Николай Малых

nmalykh@protocols.ru




Краткий обзор репозиториев P4 на Github

PDF

Здесь кратко описаны репозитории общего пользования p4lang.

Если нужна компиляция исходного кода P414 или P416 для модели поведения bmv2 с использованием консольного интерфейса или thrift API для работы с таблицами, потребуется локально установить и собрать два приведенных ниже репозитория.

  • p4c — прототип компилятора P416 (поддерживается также код P414);
  • behavioral-model — переопределение модели поведения на языке C++ без автоматического создания кода.

Репозитории p4lang

В Github p4lang является именем «организации», поддерживающей набор репозиториев, связанных с языком P4. Ниже перечислены эти репозитории по состоянию на сентябрь 2020 г.

behavioral-model

Переопределение модели поведения behavioral-model (ее часто называют bmv2 — сокращение от Behavioral Model Version 2) на языке C++ без автоматического создания кода.

Первой версией модели служил вывод кода из репозитория p4c-behavioral (компилятор P4 в C). Изменение программы P4 требовало повторной компиляции в новую программу C и последующей ее компиляции. Модель bmv2 больше напоминает интерпретатор для всех возможных программ P4, который настраивает свое поведение в соответствии с конфигурационным файлом bmv2 JSON, создаваемым компилятором p4c. При изменении программы P4 требуется повторная компиляция лишь этой программы без повтора компиляции bmv2.

education

P4 для обучения (материалы рабочей группы P4 Education).

governance

Wiki с документами проекта P4. В настоящее время репозиторий пуст.

grpc (ветвь grpc/grpc)

Обобщенная модель работы с вызовами удаленных процедур (C, C++, Python, Ruby, Objective-C, PHP, C#).

hackathons

Код, разработанный на мероприятиях P4 Hackathon.

mininet (ветвь mininet/mininet)

Эмулятор для работы с программно-определяемыми сетями (SDN), http://mininet.org.

ntf

Платформа для сетевых тестов.

p4-applications

Репозиторий рабочей группы P4 Applications, содержащий приложения и документацию по приложениям телеметрии в P4.

p4-build

Инфраструктура, требуемая для генерации, сборки и установки библиотеки PD для программ P4.

p4-hlir

Написанный на языке Python компилятор для программ P414 (v1.0.x и v1.1.x), генерирующий высокоуровневое промежуточное представление (High Level Intermediate Representation), на основе которого можно создавать компилятор back-end. Создает в памяти объекты Python, представляющие объекты код P4 (заголовки, таблицы, списки полей и т. п.). Выполняет независимые от целевой платформы семантические проверки, включая ссылки на неопределенные таблицы или поля, исключение неиспользуемых таблиц и действий.

p4-hlir не понимает программ P416.

p4-spec

Спецификации языка P4 разных версий, PSA (Portable Switch Architecture), INT (Inband Network Telemetry).

p4app

Инструменты для сборки, запуска, отладки и тестирования программ P4.

p4c

Прототип компилятора (front-end) P416, поддерживающий также программы P414. Репозиторий включает также bmv2 и другие варианты компиляторов back-end.

p4c-behavioral

Устаревший компилятор P4 для модели поведения (behavioral model). Заменен behavioral-model.

Компилятор p4c-behavioral использует p4-hlir в качестве front-end для синтаксического анализа исходного кода и дает на выходе промежуточное представление IR, из которого создается код C/C++ для модели поведения версии 1 (не подходит для bmv2).

При установке этого репозитория могут возникать конфликты с p4c-bm, поскольку оба устанавливают модуль Python p4c-bm.

p4c-bm

Устаревшая программа генерации конфигурационных файлов JSON для bmv2, а также кода C/C++ PD. Не развивается. Компилятор p4c-bm использует p4-hlir в качестве front-end для синтаксического анализа исходного кода и создает промежуточное представление IR, из которого может быть сгенерирован конфигурационный файл bmv2 JSON. Может также генерировать код C++ PD.

p4-constraints

Расширение языка P4 для анонсирования ограничений. См. презентацию. Ограничения могут быть реализованы с помощью библиотеки p4-constraints, представленной в репозитории.

p4factory

Устаревшая тестовая реализация спецификации INT (Inband Network Telemetry).

p4lang.github.io

Некая информация с сайта P4.org, в том числе анонсы мероприятий.

p4ofagent

Агент Openflow для плоскости данных P4.

p4runtime

Спецификации P4Runtime — API плоскости управления.

p4runtime-shell

Интерактивная оболочка (shell) Python для P4Runtime.

papers

Несколько старых статей, связанных с P4.

PI

Реализация сервера P4Runtime.

protobuf (ветвь protocolbuffers/protobuf)

Реализация механизмов Protocol Buffers для сериализации структурированных данных от компании Google.

ptf

Модель тестирования плоскости данных на основе Python.

rules_protobuf (ветвь pubref/rules_protobuf)

Правила Bazel для создания буферов протоколов и служб gRPC (java, c++, go и т. п.)

SAI (ветвь opencomputeproject/SAI)

Реализация абстрактного интерфейса SAI, определяющего API для независимого от производителя управления элементами пересылки (ASIC, NPU, программные коммутаторы).

scapy-vxlan

Реализация scapy от компании Barefoot с поддержкой дополнительных заголовков пакетов, включая VXLAN.

switch

Программа switch — пример реализации коммутатора на языке P4, включающая API, SAI и Netlink.

third-party

Сторонние программы, от которых зависит p4lang.

thrift (ветвь apache/thrift)

Зеркало репозитория Apache Thrift.

tutorials

Учебные материалы по языку P4.

Николай Малых

nmalykh@protocols.ru




Архитектура переносимых коммутаторов PSA

PDF

Приведенный ниже включаемый файл (include) с переведенными на русский язык комментариями содержит определения для архитектуры PSA. Файл psa.p4 размещается в каталоге p4include пакета p4c (https://github.com/p4lang/p4c/blob/master/p4include/psa.p4).

Этот файл включается в программы P4, работающие с прототипом коммутатора psa_switch на основе модели BMV2.

/* Copyright 2013-present Barefoot Networks, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0 

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#ifndef __PSA_P4__
#define __PSA_P4__

#include<core.p4>

#ifndef _PORTABLE_SWITCH_ARCHITECTURE_P4_
#define _PORTABLE_SWITCH_ARCHITECTURE_P4_

/**
 *   Объявления P4-16 для архитектуры PSA
 */

/**
 * Эти типы нужно определить до включения файла архитектуры, а затем 
 * следует определить защиту макросов.
 */
#define PSA_ON_BMV2_CORE_TYPES
#ifdef PSA_ON_BMV2_CORE_TYPES
/* Показанные ниже размеры в битах относятся к платформе BMv2 psa_switch.
 * Размер этих типов не требуется сохранять в других реализациях PSA. 
 * Каждая реализация может указать свои размеры в форме bit<W> с нужным W.
 * Одной из причин приведенного здесь выбора является реализация PSA в
 * модели BMv2. Другая заключается в упрощении компиляции этого файла и 
 * примера программы PSA (P4), включающей файл.
 *
 * Размеры в битах для BMv2 psa_switch выбраны так, чтобы они совпадали с
 * соответствующими типами InHeader. Это упрощает реализацию P4Runtime для
 * BMv2 psa_switch. */

/* В определениях используется typedef, а не type, что просто задает другие
 * имена для типа bit<W> с показанным значением W. В отличие от определений
 * type значения, объявленные с typedef, можно свободно смешивать в 
 * выражениях со значениями типа bit<W>. Значения, объявленные с type,
 * нельзя свободно смешивать, пока они не приведены к соответствующему типу
 * typedef. Это может быть неудобно для арифметических операций, зато можно
 * пометить нужным типом все значения, объявленные с type, при генерации
 * API плоскости управления.
 *
 * Отметим, что размер typedef <name>Uint_t всегда совпадает с type <name>_t. */
typedef bit<32> PortIdUint_t;
typedef bit<32> MulticastGroupUint_t;
typedef bit<16> CloneSessionIdUint_t;
typedef bit<8>  ClassOfServiceUint_t;
typedef bit<16> PacketLengthUint_t;
typedef bit<16> EgressInstanceUint_t;
typedef bit<64> TimestampUint_t;

/* Отметим, что clone_spec в BMv2 simple_switch v1model имеет размер 32 бита, 
 * но используется так, что 16 битов содержат идентификатор сеанса клонирования
 * и другие 16 битов - численный идентификатор field_list. Лишь 16 битов
 * идентификатора сессии можно сравнивать с типов CloneSessionIdUint_t. См.
 * https://github.com/p4lang/behavioral-model/blob/master/targets/simple_switch/simple_switch.cpp 
 */

@p4runtime_translation("p4.org/psa/v1/PortId_t", 32)
type PortIdUint_t         PortId_t;
@p4runtime_translation("p4.org/psa/v1/MulticastGroup_t", 32)
type MulticastGroupUint_t MulticastGroup_t;
@p4runtime_translation("p4.org/psa/v1/CloneSessionId_t", 16)
type CloneSessionIdUint_t CloneSessionId_t;
@p4runtime_translation("p4.org/psa/v1/ClassOfService_t", 8)
type ClassOfServiceUint_t ClassOfService_t;
@p4runtime_translation("p4.org/psa/v1/PacketLength_t", 16)
type PacketLengthUint_t   PacketLength_t;
@p4runtime_translation("p4.org/psa/v1/EgressInstance_t", 16)
type EgressInstanceUint_t EgressInstance_t;
@p4runtime_translation("p4.org/psa/v1/Timestamp_t", 64)
type TimestampUint_t      Timestamp_t;
typedef error   ParserError_t;

const PortId_t PSA_PORT_RECIRCULATE = (PortId_t) 0xfffffffa;
const PortId_t PSA_PORT_CPU = (PortId_t) 0xfffffffd;

const CloneSessionId_t PSA_CLONE_SESSION_TO_CPU = (CloneSessionId_t) 0;

#endif  // PSA_ON_BMV2_CORE_TYPES

#ifndef PSA_ON_BMV2_CORE_TYPES
#error "Please define the following types for PSA and the PSA_EXAMPLE_CORE_TYPES macro"
// BEGIN:Type_defns
/* В определениях используется typedef, а не type, что просто задает другие
 * имена для типа bit<W> с показанным значением W. В отличие от определений
 * type значения, объявленные с typedef, можно свободно смешивать в 
 * выражениях со значениями типа bit<W>. Значения, объявленные с type,
 * нельзя свободно смешивать, пока они не приведены к соответствующему типу
 * typedef. Это может быть неудобно для арифметических операций, зато можно
 * пометить нужным типом все значения, объявленные с type, при генерации
 * API плоскости управления.
 *
 * Отметим, что размер typedef <name>Uint_t всегда совпадает с type <name>_t. */
typedef bit<unspecified> PortIdUint_t;
typedef bit<unspecified> MulticastGroupUint_t;
typedef bit<unspecified> CloneSessionIdUint_t;
typedef bit<unspecified> ClassOfServiceUint_t;
typedef bit<unspecified> PacketLengthUint_t;
typedef bit<unspecified> EgressInstanceUint_t;
typedef bit<unspecified> TimestampUint_t;

@p4runtime_translation("p4.org/psa/v1/PortId_t", 32)
type PortIdUint_t         PortId_t;
@p4runtime_translation("p4.org/psa/v1/MulticastGroup_t", 32)
type MulticastGroupUint_t MulticastGroup_t;
@p4runtime_translation("p4.org/psa/v1/CloneSessionId_t", 16)
type CloneSessionIdUint_t CloneSessionId_t;
@p4runtime_translation("p4.org/psa/v1/ClassOfService_t", 8)
type ClassOfServiceUint_t ClassOfService_t;
@p4runtime_translation("p4.org/psa/v1/PacketLength_t", 16)
type PacketLengthUint_t   PacketLength_t;
@p4runtime_translation("p4.org/psa/v1/EgressInstance_t", 16)
type EgressInstanceUint_t EgressInstance_t;
@p4runtime_translation("p4.org/psa/v1/Timestamp_t", 64)
type TimestampUint_t      Timestamp_t;
typedef error   ParserError_t;

const PortId_t PSA_PORT_RECIRCULATE = (PortId_t) unspecified;
const PortId_t PSA_PORT_CPU = (PortId_t) unspecified;

const CloneSessionId_t PSA_CLONE_SESSION_TO_CPU = (CloneSessiontId_t) unspecified;
// END:Type_defns
#endif  // #ifndef PSA_EXAMPLE_CORE_TYPES

// BEGIN:Type_defns2
/* Все типы с InHeader в имени предназначены для передачи лишь значений
 * соответствующих типов из заголовков пакета между устройством PSA и
 * управляющей им программой сервера P4Runtime.
 *
 * Предполагается, что размер будет не меньше, чем у любого устройства PSA
 * для данного типа. Таким образом, эти типы могут также быть полезны для
 * определения заголовков пакета, передаваемых между устройством PSA и
 * другими устройствами без прохождения через сервер P4Runtime (например,
 * для отправки пакетов контроллеру или системе сбора данных с 
 * использованием скорости передачи пакетов выше, чем может обработать 
 * сервер P4Runtime). При использовании для таких целей не предъявляется
 * требования к плоскости данных PSA по автоматическому численному
 * преобразованию этих типов, которое происходило бы при прохождении
 * заголовков через сервер P4Runtime. Все нужные преобразования следует
 * выполнять программисту P4 явно в кода программы.
 *
 * Все размеры должны быть кратны 8, поэтому любое подмножество этих
 * полей можно использовать в одном определении заголовка P4, даже в 
 * реализациях P4, ограничивающих заголовки полями с общим размером, 
 * кратным 8 битам. */

/* См. комментарий выше (PortIdUint_t). */
typedef bit<32> PortIdInHeaderUint_t;
typedef bit<32> MulticastGroupInHeaderUint_t;
typedef bit<16> CloneSessionIdInHeaderUint_t;
typedef bit<8>  ClassOfServiceInHeaderUint_t;
typedef bit<16> PacketLengthInHeaderUint_t;
typedef bit<16> EgressInstanceInHeaderUint_t;
typedef bit<64> TimestampInHeaderUint_t;

@p4runtime_translation("p4.org/psa/v1/PortIdInHeader_t", 32)
type  PortIdInHeaderUint_t         PortIdInHeader_t;
@p4runtime_translation("p4.org/psa/v1/MulticastGroupInHeader_t", 32)
type  MulticastGroupInHeaderUint_t MulticastGroupInHeader_t;
@p4runtime_translation("p4.org/psa/v1/CloneSessionIdInHeader_t", 16)
type  CloneSessionIdInHeaderUint_t CloneSessionIdInHeader_t;
@p4runtime_translation("p4.org/psa/v1/ClassOfServiceInHeader_t", 8)
type  ClassOfServiceInHeaderUint_t ClassOfServiceInHeader_t;
@p4runtime_translation("p4.org/psa/v1/PacketLengthInHeader_t", 16)
type  PacketLengthInHeaderUint_t   PacketLengthInHeader_t;
@p4runtime_translation("p4.org/psa/v1/EgressInstanceInHeader_t", 16)
type  EgressInstanceInHeaderUint_t EgressInstanceInHeader_t;
@p4runtime_translation("p4.org/psa/v1/TimestampInHeader_t", 64)
type  TimestampInHeaderUint_t      TimestampInHeader_t;
// END:Type_defns2

/* Функции _int_to_header были написаны для преобразования значений типа
 * <name>_t (значение внутри пути данных) в значения типа <name>InHeader_t
 * в заголовке, который будет передан в порт CPU.
 *
 * Функции _header_to_int  были написаны для преобразования значений в
 * обратном направлении, обычно при назначении из заголовка, полученного из
 * CPU, значению, которое будет использоваться в остальном коде программы.
 *
 * Причина этих трех преобразований заключается в том, что каждый из этих
 * типов объявлен через P4_16 type, поэтому без приведения типа значения 
 * можно присваивать лишь идентичному типу. Первое приведение меняет
 * исходный тип на bit<W1> с тем же размером W1. Второе приведение меняет 
 * размер путем добавления нулей в начале или отбрасывания старших битов.
 * Третье приведение меняет тип bit<W2> на окончательный тип с размером W2. */

PortId_t psa_PortId_header_to_int (in PortIdInHeader_t x) {
    return (PortId_t) (PortIdUint_t) (PortIdInHeaderUint_t) x;
}
MulticastGroup_t psa_MulticastGroup_header_to_int (in MulticastGroupInHeader_t x) {
    return (MulticastGroup_t) (MulticastGroupUint_t) (MulticastGroupInHeaderUint_t) x;
}
CloneSessionId_t psa_CloneSessionId_header_to_int (in CloneSessionIdInHeader_t x) {
    return (CloneSessionId_t) (CloneSessionIdUint_t) (CloneSessionIdInHeaderUint_t) x;
}
ClassOfService_t psa_ClassOfService_header_to_int (in ClassOfServiceInHeader_t x) {
    return (ClassOfService_t) (ClassOfServiceUint_t) (ClassOfServiceInHeaderUint_t) x;
}
PacketLength_t psa_PacketLength_header_to_int (in PacketLengthInHeader_t x) {
    return (PacketLength_t) (PacketLengthUint_t) (PacketLengthInHeaderUint_t) x;
}
EgressInstance_t psa_EgressInstance_header_to_int (in EgressInstanceInHeader_t x) {
    return (EgressInstance_t) (EgressInstanceUint_t) (EgressInstanceInHeaderUint_t) x;
}
Timestamp_t psa_Timestamp_header_to_int (in TimestampInHeader_t x) {
    return (Timestamp_t) (TimestampUint_t) (TimestampInHeaderUint_t) x;
}

PortIdInHeader_t psa_PortId_int_to_header (in PortId_t x) {
    return (PortIdInHeader_t) (PortIdInHeaderUint_t) (PortIdUint_t) x;
}
MulticastGroupInHeader_t psa_MulticastGroup_int_to_header (in MulticastGroup_t x) {
    return (MulticastGroupInHeader_t) (MulticastGroupInHeaderUint_t) (MulticastGroupUint_t) x;
}
CloneSessionIdInHeader_t psa_CloneSessionId_int_to_header (in CloneSessionId_t x) {
    return (CloneSessionIdInHeader_t) (CloneSessionIdInHeaderUint_t) (CloneSessionIdUint_t) x;
}
ClassOfServiceInHeader_t psa_ClassOfService_int_to_header (in ClassOfService_t x) {
    return (ClassOfServiceInHeader_t) (ClassOfServiceInHeaderUint_t) (ClassOfServiceUint_t) x;
}
PacketLengthInHeader_t psa_PacketLength_int_to_header (in PacketLength_t x) {
    return (PacketLengthInHeader_t) (PacketLengthInHeaderUint_t) (PacketLengthUint_t) x;
}
EgressInstanceInHeader_t psa_EgressInstance_int_to_header (in EgressInstance_t x) {
    return (EgressInstanceInHeader_t) (EgressInstanceInHeaderUint_t) (EgressInstanceUint_t) x;
}
TimestampInHeader_t psa_Timestamp_int_to_header (in Timestamp_t x) {
    return (TimestampInHeader_t) (TimestampInHeaderUint_t) (TimestampUint_t) x;
}

/// Диапазон поддерживаемых значений для свойств таблицы psa_idle_timeout
enum PSA_IdleTimeout_t {
  NO_TIMEOUT,
  NOTIFY_CONTROL
};

// BEGIN:Metadata_types
enum PSA_PacketPath_t {
    NORMAL,     /// Пакет, полученный ingress и не относящийся к перечисленным ниже.
    NORMAL_UNICAST,   /// Обычный индивидуальный пакет, принятый egress.
    NORMAL_MULTICAST, /// Обычный групповой пакет, принятый egress.
    CLONE_I2E,  /// Пакет, созданный клонированием в ingress и предназначенный
                /// для egress.
    CLONE_E2E,  /// Пакет, созданный клонированием в egress и предназначенный
                /// для egress.
    RESUBMIT,   /// Пакет, принятый в результате операции resubmit.
    RECIRCULATE /// Пакет, принятый в результате операции recirculate.
}

struct psa_ingress_parser_input_metadata_t {
  PortId_t                 ingress_port;
  PSA_PacketPath_t         packet_path;
}

struct psa_egress_parser_input_metadata_t {
  PortId_t                 egress_port;
  PSA_PacketPath_t         packet_path;
}

struct psa_ingress_input_metadata_t {
  // Все эти значения инициализируются архитектурой до начала выполнения
  // бллока управления Ingress.
  PortId_t                 ingress_port;
  PSA_PacketPath_t         packet_path;
  Timestamp_t              ingress_timestamp;
  ParserError_t            parser_error;
}
// BEGIN:Metadata_ingress_output
struct psa_ingress_output_metadata_t {
  // В комментариях к полям приведены исходные значения в момент начала
  // выполнения блока управления Ingress.
  ClassOfService_t         class_of_service; // 0
  bool                     clone;            // false
  CloneSessionId_t         clone_session_id; // не определено
  bool                     drop;             // true
  bool                     resubmit;         // false
  MulticastGroup_t         multicast_group;  // 0
  PortId_t                 egress_port;      // не определено
}
// END:Metadata_ingress_output
struct psa_egress_input_metadata_t {
  ClassOfService_t	class_of_service;
  PortId_t            egress_port;
  PSA_PacketPath_t    packet_path;
  EgressInstance_t    instance;       /// Экземпляр приходит из PacketReplicationEngine
  Timestamp_t         egress_timestamp;
  ParserError_t       parser_error;
}

/// Эта структура является входным (in) параметром для выходного сборщика.
/// Она включает данные для выходного сборщика, позволяющие решить вопрос
/// о рециркуляции пакета.
struct psa_egress_deparser_input_metadata_t {
  PortId_t                 egress_port;
}
// BEGIN:Metadata_egress_output
struct psa_egress_output_metadata_t {
  // В комментариях к полям приведены исходные значения в момент начала
  // выполнения блока управления Egress.
  bool                     clone;         // false
  CloneSessionId_t         clone_session_id; // не определено
  bool                     drop;          // false
}
// END:Metadata_egress_output
// END:Metadata_types

/// При выполнении IngressDeparser функция psa_clone_i2e возвращает true
/// тогда и только тогда, когда создается клон обрабатываемого пакета для 
/// выхода. Какие-либо назначения параметра clone_i2e_meta в
/// IngressDeparser могут выполняться лишь внутри оператора if, который
/// позволяет выполнять такое назначение лишь в случае возврата функцией 
/// psa_clone_i2e(istd) значения true. psa_clone_i2e можно реализовать 
/// путем возврата istd.clone

@pure
extern bool psa_clone_i2e(in psa_ingress_output_metadata_t istd);

/// При выполнении IngressDeparser функция psa_resubmit возвращает true
/// тогда и только тогда, когда пакет представляется заново (resubmit).
/// Какие-либо назначения параметра resubmit_meta в  IngressDeparser
/// могут выполняться лишь внутри оператора if, который  позволяет
/// выполнять такое назначение лишь в случае возврата функцией 
/// psa_resubmit(istd) значения true. psa_resubmit можно реализовать 
/// путем возврата (!istd.drop && istd.resubmit)


@pure
extern bool psa_resubmit(in psa_ingress_output_metadata_t istd);

/// При выполнении IngressDeparser функция psa_normal возвращает true
/// тогда и только тогда, когда пакет передается на выход «обычным» 
/// путем как индивидуальный или групповой. Какие-либо назначения 
/// параметра resubmit_meta в IngressDeparser могут выполняться лишь 
/// внутри оператора if, который позволяет выполнять такое назначение 
/// лишь в случае возврата функцией psa_normal(istd) значения true. 
/// psa_normal можно реализовать путем возврата (!istd.drop && istd.resubmit)

@pure
extern bool psa_normal(in psa_ingress_output_metadata_t istd);

/// При выполнении EgressDeparser функция psa_clone_e2e возвращает true
/// тогда и только тогда, когда создается клон обрабатываемого пакета
/// для выхода. Какие-либо назначения параметра clone_e2e_meta в 
/// EgressDeparser могут выполняться лишь внутри оператора if, который
/// позволяет выполнять такое назначение лишь в случае возврата функцией
/// psa_clone_e2e(istd) значения true. psa_clone_e2e можно реализовать
/// путем возврата istd.clone

@pure
extern bool psa_clone_e2e(in psa_egress_output_metadata_t istd);

/// При выполнении EgressDeparser функция psa_recirculate возвращает true
/// тогда и только тогда, когда пакет передается в рециркуляцию. 
/// Какие-либо назначения параметра recirculate_meta в EgressDeparser
/// могут выполняться лишь внутри оператора if, который позволяет
/// выполнять такое назначение лишь в случае возврата функцией 
/// psa_recirculate(istd) значения true. psa_recirculate можно 
/// реализовать путем возврата (!istd.drop && (edstd.egress_port
/// == PSA_PORT_RECIRCULATE))

@pure
extern bool psa_recirculate(in psa_egress_output_metadata_t istd,
                            in psa_egress_deparser_input_metadata_t edstd);


extern void assert(in bool check);
extern void assume(in bool check);

// BEGIN:Match_kinds
match_kind {
    range,   /// Служит для представления интервалов min..max.
    selector /// Служит для динамического выбора действия с помощью 
             /// внешнего метода ActionSelector.
}
// END:Match_kinds

// BEGIN:Action_send_to_port
/// Меняет выходные метаданные ingress для отправки пакета на выходную
/// обработку с последующим выводом в egress_port (при выходной 
/// обработке пакет может быть отброшен).

/// Это действие не влияет на операции clone или resubmit.

@noWarnUnused
action send_to_port(inout psa_ingress_output_metadata_t meta,
                    in PortId_t egress_port)
{
    meta.drop = false;
    meta.multicast_group = (MulticastGroup_t) 0;
    meta.egress_port = egress_port;
}
// END:Action_send_to_port

// BEGIN:Action_multicast
/// Меняет выходные метаданные ingress для создания копий пакета, 
/// отправляемых на выходную обработку.

/// Это действие не влияет на операции clone или resubmit.

@noWarnUnused
action multicast(inout psa_ingress_output_metadata_t meta,
                 in MulticastGroup_t multicast_group)
{
    meta.drop = false;
    meta.multicast_group = multicast_group;
}
// END:Action_multicast

// BEGIN:Action_ingress_drop
/// Меняет выходные метаданные ingress для для обычной выходной
/// обработки.

/// Это действие не влияет на операцию clone, но предотвращает 
/// повторное представления пакета (resubmit).

@noWarnUnused
action ingress_drop(inout psa_ingress_output_metadata_t meta)
{
    meta.drop = true;
}
// END:Action_ingress_drop

// BEGIN:Action_egress_drop
/// Меняет выходные метаданные egress для передачи пакета из устройства.

/// Это действие не влияет на операцию clone.

@noWarnUnused
action egress_drop(inout psa_egress_output_metadata_t meta)
{
    meta.drop = true;
}
// END:Action_egress_drop

extern PacketReplicationEngine {
    PacketReplicationEngine();
    // Для этого объекта нет методов, вызываемых из программ P4. Метод
    // будет иметь экземпляр с именем, которое плоскость управления
    // может использовать для вызова объекта через API.
}

extern BufferingQueueingEngine {
    BufferingQueueingEngine();
    // Для этого объекта нет методов, вызываемых из программ P4. 
    // См. предыдущий комментарий. 
}

// BEGIN:Hash_algorithms
enum PSA_HashAlgorithm_t {
  IDENTITY,
  CRC32,
  CRC32_CUSTOM,
  CRC16,
  CRC16_CUSTOM,
  ONES_COMPLEMENT16,  /// 16-битовая контрольная сумма с дополнением до 1,
                      /// используемая в заголовках IPv4, TCP и UDP.
  TARGET_DEFAULT      /// Определяется реализацией целевой платформы.
}
// END:Hash_algorithms

// BEGIN:Hash_extern
extern Hash<O> {
  /// Constructor
  Hash(PSA_HashAlgorithm_t algo);

  /// Расчет хэш-значения для данных.
  /// @param data - данные для расчета хэш-значения.
  /// @return - хэш-значение.
  @pure
  O get_hash<D>(in D data);

  /// Расчет хэш-значения для данных с модулем max и добавлением base.
  /// @param base - минимальное возвращаемое значение.
  /// @param data - данные для расчета хэш-значения.
  /// @param max - хэш-значение делится на max.
  ///        Реализация может ограничивать поддерживаемое максимальное
  ///        значение (например, 32 или 256), а также может поддерживать
  ///        для него лишь степени 2. Разработчикам P4 следует выбирать
  ///        такие значения для обеспечения переносимости.
  /// @return (base + (h % max)), где h - хэш-значение.
  @pure
  O get_hash<T, D>(in T base, in D data, in T max);
}
// END:Hash_extern

// BEGIN:Checksum_extern
extern Checksum<W> {
  /// Constructor
  Checksum(PSA_HashAlgorithm_t hash);

  /// Сбрасывает внутреннее состояние и готовит модуль к расчету. Каждый
  /// экземпляр объекта Checksum автоматически инициализируется как при
  /// вызове clear(). Инициализация выполняется при каждом создании 
  /// экземпляра объекта, независимо от применения в анализаторе или 
  /// элементе управления. Все состояния, поддерживаемые объектом  
  /// Checksum независимы между пакетами.
  void clear();

  /// Добавление данных в контрольную сумму.
  void update<T>(in T data);

  /// Получение контрольной суммы для добавленных (и не удаленных) 
  /// с момента последней очистки данных.
  @noSideEffects
  W    get();
}
// END:Checksum_extern

// BEGIN:InternetChecksum_extern
// Контрольная сумма на основе алгоритма ONES_COMPLEMENT16, используемая в 
// IPv4, TCP и UDP. Поддерживается инкрементальное обновление методом 
// subtract (см. IETF RFC 1624).
extern InternetChecksum {
  /// Конструктор
  InternetChecksum();

  /// Сбрасывает внутреннее состояние и готовит модуль к расчету. Каждый
  /// экземпляр объекта InternetChecksum автоматически инициализируется как 
  /// при вызове clear(). Инициализация выполняется при каждом создании 
  /// запуске анализатора или элемента управления, где применяется объект. 
  /// Все состояния, поддерживаемые объектом независимы между пакетами.
  void clear();

  /// Добавляет в расчет контрольной суммы данные data, размер которых
  /// должен быть кратным 16 битам.
  void add<T>(in T data);

  /// Исключает из расчета контрольной суммы данные data, размер которых
  /// должен быть кратным 16 битам.
  void subtract<T>(in T data);

  /// Возвращает контрольную сумму для данных, добавленных (и не удаленных)
  /// после предшествующего вызова clear.
  @noSideEffects
  bit<16> get();

  /// Возвращает состояние расчета контрольной суммы для использования при
  /// последующем вызове метода set_state.
  @noSideEffects
  bit<16> get_state();

  /// Возвращает состояние экземпляра InternetChecksum к возвращенному при
  /// предшествующем вызове метода get_state. Состояние может возвращаться
  /// для одного или разных экземпляров InternetChecksum.
  void set_state(in bit<16> checksum_state);
}
// END:InternetChecksum_extern

// BEGIN:CounterType_defn
enum PSA_CounterType_t {
    PACKETS,
    BYTES,
    PACKETS_AND_BYTES
}
// END:CounterType_defn

// BEGIN:Counter_extern
/// Опосредованный счетчик с n_counters независимых значений, где каждое
/// значение имеет заданный плоскостью данных размер W.

extern Counter<W, S> {
  Counter(bit<32> n_counters, PSA_CounterType_t type);
  void count(in S index);

  /*
  /// API плоскости управления использует 64-битовые значения считчиков. 
  /// Это не указывает размеры счетчиков в плоскости управления. 
  /// Предполагается, что программы управления периодически считывают
  /// значения счетчиков плоскости данных и аккумулирует их в счетчиках
  /// большего размера, в которых максимальное значение достигается реже.
  /// 64-битовые счетчики позволяют работать при скорости порта 100 Гбит/с
  /// в течение 46 лет без переполнения.

  @ControlPlaneAPI
  {
    bit<64> read      (in S index);
    bit<64> sync_read (in S index);
    void set          (in S index, in bit<64> seed);
    void reset        (in S index);
    void start        (in S index);
    void stop         (in S index);
  }
  */
}
// END:Counter_extern

// BEGIN:DirectCounter_extern
extern DirectCounter<W> {
  DirectCounter(PSA_CounterType_t type);
  void count();

  /*
  @ControlPlaneAPI
  {
    W    read<W>      (in TableEntry key);
    W    sync_read<W> (in TableEntry key);
    void set          (in TableEntry key, in W seed);
    void reset        (in TableEntry key);
    void start        (in TableEntry key);
    void stop         (in TableEntry key);
  }
  */
}
// END:DirectCounter_extern

// BEGIN:MeterType_defn
enum PSA_MeterType_t {
    PACKETS,
    BYTES
}
// END:MeterType_defn

// BEGIN:MeterColor_defn
enum PSA_MeterColor_t { RED, GREEN, YELLOW }
// END:MeterColor_defn

// BEGIN:Meter_extern
// Индексируемый измеритель с n_meters независимых состояний.

extern Meter<S> {
  Meter(bit<32> n_meters, PSA_MeterType_t type);

  // Этот метод служит для «перекрашивания» трафика (см. RFC 2698). 
  // «Цвет» пакета перед вызовом метода указан параметром color.
  PSA_MeterColor_t execute(in S index, in PSA_MeterColor_t color);

  // Этот метод служит для «окрашивания» трафика вслепую (см. RFC 2698). 
  // Метод может быть реализован вызовом execute(index, MeterColor_t.GREEN).
  PSA_MeterColor_t execute(in S index);

  /*
  @ControlPlaneAPI
  {
    reset(in MeterColor_t color);
    setParams(in S index, in MeterConfig config);
    getParams(in S index, out MeterConfig config);
  }
  */
}
// END:Meter_extern

// BEGIN:DirectMeter_extern
extern DirectMeter {
  DirectMeter(PSA_MeterType_t type);
  // См. аналогичный метод для extern Meter.
  PSA_MeterColor_t execute(in PSA_MeterColor_t color);
  PSA_MeterColor_t execute();

  /*
  @ControlPlaneAPI
  {
    reset(in TableEntry entry, in MeterColor_t color);
    void setConfig(in TableEntry entry, in MeterConfig config);
    void getConfig(in TableEntry entry, out MeterConfig config);
  }
  */
}
// END:DirectMeter_extern

// BEGIN:Register_extern
extern Register<T, S> {
  /// Создание массива из <size> регистров с неопределенными значениями.
  Register(bit<32> size);
  ///  Создание массива из <size> регистров с заданными значениями.
  Register(bit<32> size, T initial_value);

  @noSideEffects
  T    read  (in S index);
  void write (in S index, in T value);

  /*
  @ControlPlaneAPI
  {
    T    read<T>      (in S index);
    void set          (in S index, in T seed);
    void reset        (in S index);
  }
  */
}
// END:Register_extern

// BEGIN:Random_extern
extern Random<T> {

  /// Возвращает случайное значение из диапазона [min, max].
  /// Реализациям разрешается поддерживать лишь диапазоны, где
  /// значение (max - min + 1) является степенью 2. Программистам P4
  /// следует ограничивать аргументы значениями, обеспечивающими
  /// переносимость программы.

  Random(T min, T max);
  T read();

  /*
  @ControlPlaneAPI
  {
    void reset();
    void setSeed(in T seed);
  }
  */
}
// END:Random_extern

// BEGIN:ActionProfile_extern
extern ActionProfile {
  /// Construct an action profile of 'size' entries
  ActionProfile(bit<32> size);

  /*
  @ControlPlaneAPI
  {
     entry_handle add_member    (action_ref, action_data);
     void         delete_member (entry_handle);
     entry_handle modify_member (entry_handle, action_ref, action_data);
  }
  */
}
// END:ActionProfile_extern

// BEGIN:ActionSelector_extern
extern ActionSelector {
  /// Создает селектор действий с числом записей size.
  /// @param algo - алгоритм хэширования для выбора записи в группе;
  /// @param size - число записей в селекторе действий;
  /// @param outputWidth - размер ключа выбора.
  ActionSelector(PSA_HashAlgorithm_t algo, bit<32> size, bit<32> outputWidth);

  /*
  @ControlPlaneAPI
  {
     entry_handle add_member        (action_ref, action_data);
     void         delete_member     (entry_handle);
     entry_handle modify_member     (entry_handle, action_ref, action_data);
     group_handle create_group      ();
     void         delete_group      (group_handle);
     void         add_to_group      (group_handle, entry_handle);
     void         delete_from_group (group_handle, entry_handle);
  }
  */
}
// END:ActionSelector_extern

// BEGIN:Digest_extern
extern Digest<T> {
  Digest();                       /// Определяет поток сообщений (digest) для 
                                  /// плоскости управления.
  void pack(in T data);           /// Передает данные в поток сообщений.
  /*
  @ControlPlaneAPI
  {
  T data;                           /// Если T является списком, плоскость управления
                                    /// создает структуру (struct).
  int unpack(T& data);              /// Неупакованные данные находятся в T&, int 
                                    /// int возвращает код состояния.
  }
  */
}
// END:Digest_extern

// BEGIN:Programmable_blocks
parser IngressParser<H, M, RESUBM, RECIRCM>(
    packet_in buffer,
    out H parsed_hdr,
    inout M user_meta,
    in psa_ingress_parser_input_metadata_t istd,
    in RESUBM resubmit_meta,
    in RECIRCM recirculate_meta);

control Ingress<H, M>(
    inout H hdr, inout M user_meta,
    in    psa_ingress_input_metadata_t  istd,
    inout psa_ingress_output_metadata_t ostd);

control IngressDeparser<H, M, CI2EM, RESUBM, NM>(
    packet_out buffer,
    out CI2EM clone_i2e_meta,
    out RESUBM resubmit_meta,
    out NM normal_meta,
    inout H hdr,
    in M meta,
    in psa_ingress_output_metadata_t istd);

parser EgressParser<H, M, NM, CI2EM, CE2EM>(
    packet_in buffer,
    out H parsed_hdr,
    inout M user_meta,
    in psa_egress_parser_input_metadata_t istd,
    in NM normal_meta,
    in CI2EM clone_i2e_meta,
    in CE2EM clone_e2e_meta);

control Egress<H, M>(
    inout H hdr, inout M user_meta,
    in    psa_egress_input_metadata_t  istd,
    inout psa_egress_output_metadata_t ostd);

control EgressDeparser<H, M, CE2EM, RECIRCM>(
    packet_out buffer,
    out CE2EM clone_e2e_meta,
    out RECIRCM recirculate_meta,
    inout H hdr,
    in M meta,
    in psa_egress_output_metadata_t istd,
    in psa_egress_deparser_input_metadata_t edstd);

package IngressPipeline<IH, IM, NM, CI2EM, RESUBM, RECIRCM>(
    IngressParser<IH, IM, RESUBM, RECIRCM> ip,
    Ingress<IH, IM> ig,
    IngressDeparser<IH, IM, CI2EM, RESUBM, NM> id);

package EgressPipeline<EH, EM, NM, CI2EM, CE2EM, RECIRCM>(
    EgressParser<EH, EM, NM, CI2EM, CE2EM> ep,
    Egress<EH, EM> eg,
    EgressDeparser<EH, EM, CE2EM, RECIRCM> ed);

package PSA_Switch<IH, IM, EH, EM, NM, CI2EM, CE2EM, RESUBM, RECIRCM> (
    IngressPipeline<IH, IM, NM, CI2EM, RESUBM, RECIRCM> ingress,
    PacketReplicationEngine pre,
    EgressPipeline<EH, EM, NM, CI2EM, CE2EM, RECIRCM> egress,
    BufferingQueueingEngine bqe);

// END:Programmable_blocks

#endif  /* _PORTABLE_SWITCH_ARCHITECTURE_P4_ */

#endif   // __PSA_P4__

Николай Малых

nmalykh@protocols.ru




Архитектура uBPF

PDF

Приведенный ниже включаемый файл (include) с переведенными на русский язык комментариями содержит определения пакета ubpf в одноименной модели. Файл ubpf.p4 размещается в каталоге p4include пакета p4c и доступен по ссылке.

/*
Copyright 2019 Orange

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0 

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#ifndef _UBPF_MODEL_P4_
#define _UBPF_MODEL_P4_

#include <core.p4>

/*
 * Платформа uBPF в настоящее время позволяет пропускать (pass) или отбрасывать 
 * (drop) пакеты. По умолчанию пакеты пропускаются. Можно использовать 
 * внешнюю функцию mark_to_drop(), чтобы пометить пакет для отбрасывания. 
 * Функция mark_to_drop() меняет лишь состояние, скрытое от  пользовательской
 * программы P4 и вызывать ее следует лишь из элемента управления pipe.
 */
extern void mark_to_drop();

/*
 * Платформа uBPF в настоящее время позволяет пропускать (pass) или отбрасывать 
 * (drop) пакеты. По умолчанию пакеты пропускаются. Можно использовать 
 * внешнюю функцию mark_to_pass() чтобы пометить пакет для пропускания (это
 * отменяет предшествующее действие mark_to_drop()). Функция mark_to_drop()
 * меняет лишь состояние, скрытое от  пользовательской программы P4 
 * и вызывать ее следует лишь из элемента управления pipe.
 */
extern void mark_to_pass();


extern Register<T, S> {
  /***
   * Объект Register создается вызовом его конструктора. При вызове нужно
   * указать размер Register, определяющий максимальное число записей в
   * регистре. После создания объекта Register его можно применять в
   * действиях и блоках apply. При создании Register не инициализируется.
   */
  Register(bit<32> size);

  /***
   * Метод read() считывает состояние (T) из массива регистров по заданному
   * индексу S и возвращает значение в параметре result.
   *
   * @param index Индекс элемента массива регистров для считывания. Обычно
   *              из диапазона [0, size-1].
   * @return Возвращает результат типа T (в настоящее время только bit<W>).
   *         Если индекс находится в диапазоне, result получает значение 
   *         элемента массива регистров. При index >= size результат 
   *         становится неопределенным и его следует игнорировать.
   */
  T read  (in S index);

  void write (in S index, in T value);
}

/*
 * Функция возвращает метку текущего времени в наносекундах.
 */
extern bit<48> ubpf_time_get_ns();

enum HashAlgorithm {
    lookup3
}

/***
 * Рассчитывает хэш-функцию от значения, заданного параметром data. В силу 
 * ограничений uBPF максимальный размер data составляет bit<64>.
 *
 * Отметим, что типы всех параметров могут быть одинаковыми или разными.
 * Во втором случае их битовые размеры могут различаться.
 *
 * Результат всегда имеет размер bit<32>.
 *
 * @param D    Должен иметь тип tuple, состоящий из битовых полей (type bit<W>, int<W>) 
 *             или varbit. Максимальный размер D составляет 64 бита (ограничение uBPF).
 */
extern void hash<D>(out bit<32> result, in HashAlgorithm algo, in D data);

/***
 * Расчет контрольной суммы с помощью инкрементального обновления (RFC 1624).
 * Функция реализует расчет контрольной суммы для 16-битовых полей.
 */
extern bit<16> csum_replace2(in bit<16> csum,  // Текущая контрольная сумма.
                             in bit<16> old,   // Старое значение поля.
                             in bit<16> new);

/***
 * Расчет контрольной суммы с помощью инкрементального обновления (RFC 1624).
 * Функция реализует расчет контрольной суммы для 32-битовых полей.
 */
extern bit<16> csum_replace4(in bit<16> csum,
                             in bit<32> old,
                             in bit<32> new);

/*
 * Архитектура
 *
 * Параметр M должен иметь тип struct.
 *
 * Параметр H должен иметь тип struct, а каждый элемент структуры должен
 * быть заголовком, стеком или объединением заголовков (header_union).
 */

parser parse<H, M>(packet_in packet, out H headers, inout M meta);
control pipeline<H, M>(inout H headers, inout M meta);

/*
 * В теле сборщика (deparser) допускается лишь вызов метода packet_out.emit().
 */
@deparser
control deparser<H>(packet_out b, in H headers);

package ubpf<H, M>(parse<H, M> prs,
                pipeline<H, M> p,
                deparser<H> dprs);

#endif /* _UBPF_MODEL_P4_ */

Николай Малых

nmalykh@protocols.ru




Базовая библиотека P4 — core.p4

PDF

В спецификации языка P416 часть базовых функций была вынесена в отдельную библиотеку core.p4, содержащую базовые определения и функции языка и используемую в качестве включаемого файла (директива #include) программами P4. Ниже представлен вариант этого файла с переведенными на русский язык комментариями. Исходный файл доступен в каталоге p4includes пакета p4c или по ссылке.

/*
Copyright 2013-present Barefoot Networks, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0 

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/* Базовая библиотека P4-16, где объявлены некоторые встроенные элементы языка P4 для использования в программах P4 */

#ifndef _CORE_P4_
#define _CORE_P4_

/// Стандартные коды ошибок. Пользователи могут добавлять свои коды.
error {
    NoError,           /// Нет ошибок.
    PacketTooShort,    /// В пакете недостаточно битов для извлечения.
    NoMatch,           /// В выражении select не найдено соответствия.
    StackOutOfBounds,  /// Ссылка на недействительный элемент стека заголовков.
    HeaderTooShort,    /// Извлекается слишком много битов заголовка в поле varbit.
    ParserTimeout,     /// Превышено время выполнения кода синтаксического анализатора.
    ParserInvalidArgument  /// Операция анализатора была вызвана с неподдерживаемым
                           /// реализацией значением.
}

extern packet_in {
    /// Считывает заголовок пакета в заголовок фиксированного размера и перемещает указатель.
    /// Может приводить к ошибке PacketTooShort или StackOutOfBounds.
    /// Параметр @T должен иметь тип заголовка фиксированного размера.
    void extract<T>(out T hdr);
    /// Считывает биты из заголовка пакета в заголовок @variableSizeHeader с переменным размером
    /// и перемещает указатель.
    /// Параметр @T должен быть заголовком, содержащим в точности 1 поле 1.
    /// Может приводить к ошибке PacketTooShort, StackOutOfBounds или HeaderTooShort.
    void extract<T>(out T variableSizeHeader,
                    in bit<32> variableFieldSizeInBits);
    /// Считывает биты из пакета без перемещения указателя.
    /// Параметр @returns содержит прочитанные из пакета биты.
    /// T может быть произвольным типом фиксированного размера.
    T lookahead<T>();
    /// Перемещает указатель на заданное число битов.
    void advance(in bit<32> sizeInBits);
    /// Переменная @return содержит размер пакета в байтах. Метод может не поддерживаться архитектурой.
    bit<32> length();
}

extern packet_out {
    /// Записывает заголовок @hdr в выходной пакет, перемещая указатель.
    /// @T может иметь тип заголовка, стека или объединения заголовков, а также структурой с 
    /// такими полями.
    void emit<T>(in T hdr);
}

// В будущих версиях следует исключить приведенное ниже из файла, преобразовав в built-in

/// Проверяет предикат @check в синтаксическом анализаторе. При значении true не делает ничего,
/// в противном случае устанавливает ошибку анализатора в @toSignal и переходит в состояние reject.
extern void verify(in bool check, in error toSignal);

/// Встроенная пустая операция.
@noWarn("unused")
action NoAction() {}

/// Стандартные типы сопоставления для ключей таблиц.
/// Отдельные типы могут не поддерживаться архитектурой.
/// Архитектура может добавлять типы сопоставления.
match_kind {
    /// Точное совпадение битов
    exact,
    /// Троичное сопоставление с использованием маски.
    ternary,
    /// Наибольший совпадающий префикс.
    lpm
}

#endif  /* _CORE_P4_ */

Николай Малых

nmalykh@protocols.ru




Архитектура v1model

PDF

Приведенный ниже включаемый файл (include) с переведенными на русский язык комментариями содержит определения пакета V1Switch для архитектуры v1model и платформы BMV2. Файл v1model.p4 размещается в каталоге p4include пакета p4c (https://github.com/p4lang/p4c/blob/master/p4include/v1model.p4).

Этот файл включается в программы P4, работающие с прототипом коммутатора simple_switch на основе BMV2, включая тестовые примеры из пакета p4c (https://github.com/p4lang/p4c/tree/master/testdata).

/*
Copyright 2013-present Barefoot Networks, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0 

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/* Объявление P4-16 для модели коммутатора P4 v1.0 */

/* Примечание 1. Более подробное описание архитектуры v1model доступно
 * по приведенной ниже ссылке.
 *
 * https://github.com/p4lang/behavioral-model/blob/master/docs/simple_switch.md1 
 *
 * Примечание 2. В начале 2019 г. в рабочей группе P4 несколько раз
 * обсуждались способы вызова операций resubmit, recirculate и clone3
 * из соответствующих элементов управления (control), но значениями 
 * сохраняемых полей будут значения на момент завершения вызова
 * элемента управления. Так эти операции определены в P4_14. См.
 * https://github.com/p4lang/behavioral-model/blob/master/docs/simple_switch.md#restrictions-on-recirculate-resubmit-and-clone-operations1 
 *
 * Примечание 3. Имеются реализации P4_14, где вызов операции
 * generate_digest в field_list приводит к созданию сообщения для
 * плоскости управления, содержащего значения этих полей при завершении
 * работы элемента управления ingress, которые могут отличаться от
 * значений этих же полей в момент вызова из программы операции
 * generate_digest, если значения этих полей меняются при вызове 
 * элемента управления P4_14 ingress.
 *
 * Реализации P4_16 с моделью v1model всегда следует создавать 
 * сообщения digest, содержащие значения указанных полей на момент вызова
 * внешней функции digest. Программа P4_14 с описанным выше поведением,
 * скомпилированная с использованием p4c, может вести себя иначе.
 */

#ifndef _V1_MODEL_P4_
#define _V1_MODEL_P4_

#include "core.p4"

#ifndef V1MODEL_VERSION
#define V1MODEL_VERSION 20180101
#endif

match_kind {
    range,
    // Точное или шаблонное (соответствует любому) совпадение.
    optional,
    // Служит для реализации dynamic_action_selection.
    selector
}

const bit<32> __v1model_version = V1MODEL_VERSION;

#if V1MODEL_VERSION >= 20200408
typedef bit<9>  PortId_t;       // Не должно иметь постоянный размер?
#endif

@metadata @name("standard_metadata")
struct standard_metadata_t {
#if V1MODEL_VERSION >= 20200408
    PortId_t    ingress_port;
    PortId_t    egress_spec;
    PortId_t    egress_port;
#else
    bit<9>      ingress_port;
    bit<9>      egress_spec;
    bit<9>      egress_port;
#endif
    bit<32>     instance_type;
    bit<32>     packet_length;
    //
    // @alias служит для создания раздела field_alias в файле BMV2 JSON.
    // Псевдоним поля создает отображение имени метаданных в программе P4
    // на внутреннее имя метаданных в модели поведения (bmv2). Здесь это
    // служит для раскрытия всех метаданных, поддерживаемых simple_switch, 
    // для пользователя через standard_metadata_t.
    //
    // «Сглаживающие» поля из bmv2-ss для запроса метаданных.
    @alias("queueing_metadata.enq_timestamp")
    bit<32> enq_timestamp;
    @alias("queueing_metadata.enq_qdepth")
    bit<19> enq_qdepth;
    @alias("queueing_metadata.deq_timedelta")
    bit<32> deq_timedelta;
    /// Глубина очереди в момент извлечения пакета из нее.
    @alias("queueing_metadata.deq_qdepth")
    bit<19> deq_qdepth;

    // Внутренние метаданные
    @alias("intrinsic_metadata.ingress_global_timestamp")
    bit<48> ingress_global_timestamp;
    @alias("intrinsic_metadata.egress_global_timestamp")
    bit<48> egress_global_timestamp;
    /// Идентификатор multicast-группы (ключ для таблицы репликации mcast)
    @alias("intrinsic_metadata.mcast_grp")
    bit<16> mcast_grp;
    /// Идентификатор репликации для multicast
    @alias("intrinsic_metadata.egress_rid")
    bit<16> egress_rid;
    /// Указывает отрицательный результат verify_checksum().
    /// 1 при ошибке контрольной суммы, иначе 0.
    bit<1>  checksum_error;
    /// Ошибка при синтаксическом анализе.
    error parser_error;
    /// Задание приоритета для пакета.
    @alias("intrinsic_metadata.priority")
    bit<3> priority;
}

enum CounterType {
    packets,
    bytes,
    packets_and_bytes
}

enum MeterType {
    packets,
    bytes
}

extern counter
#if V1MODEL_VERSION >= 20200408
<I>
#endif
{
    /***
     * Объект counter (счетчик) создается вызовом конструктора. При 
     * этом создается массив состояний счетчика с набором состояний,
     * заданным параметром size. Индексы массива имеют значения  
     * [0, size-1].
     *
     * Нужно указать, будут ли учитываться только пакеты 
     * (CounterType.packets), только байты (CounterType.bytes) или
     * то и другое (CounterType.packets_and_bytes).
     *
     * Счетчики могут обновляться из программы P4, но для чтения 
     * доступны лишь плоскости управления. Если требуется чтение и 
     * запись из программы P4, следует использовать регистры.
     */
    counter(bit<32> size, CounterType type);
    /* FIXME - Аргумент size должен иметь тип int, но это нарушает
     * проверку типов
     */
 
    /***
     * count() вызывает для указанного индексом состояния считывание,
     * изменение и запись обратно в виде неделимого (atomic) блока
     * относительно обработки других пакетов. Обновление счетчиков 
     * пакетов, байтов или обоих зависит от CounterType экземпляра
     * счетчика при вызове для него конструктора.
     *
     * @param index Индекс обновляемого состояния счетчика в массиве.
     *              Обычно это значение из диапазона [0, size-1].
     *              Если index >= size, состояние счетчика не меняется.
     */
#if V1MODEL_VERSION >= 20200408
    void count(in I index);
#else
    void count(in bit<32> index);
#endif
}

extern direct_counter {
    /***
     * Объект direct_counter создается вызовом его конструктора. Нужно
     * указать подсчет только пакетов (CounterType.packets), только
     * байтов (CounterType.bytes) или обоих (CounterType.packets_and_bytes).
     * После создания объекта его можно связать с одной таблицей, добавляя
     * в ее определение указанное ниже свойство
     *     counters = <object_name>;
     *
     * Счетчики могут обновляться из программы P4, но для чтения 
     * доступны лишь плоскости управления. Если требуется чтение и 
     * запись из программы P4, следует использовать регистры.
     */
    direct_counter(CounterType type);
    /***
     * Метод count() реально не требуется в архитектуре v1model. 
     * Это связано с тем, что после привязки объекта direct_counter к
     * таблице, как описано в документации конструктора direct_counter,
     * при каждом обращении к таблице с возвратом подходящей записи
     * состояние счетчика, связанного с этой записью, считывается, 
     * изменяется и записывается обратно неделимым блоком относительно 
     * обработки других пакетов независимо от вызова метода count() из
     * тела данного действия.
     */
    void count();
}

#define V1MODEL_METER_COLOR_GREEN  0
#define V1MODEL_METER_COLOR_YELLOW 1
#define V1MODEL_METER_COLOR_RED    2

extern meter
#if V1MODEL_VERSION >= 20200408
<I>
#endif
{
    /***
     * Объект meter создается вызовом его конструктора. При этом 
     * создается массив состояний, число которых определяется параметром
     * size. Индексы массива лежат в диапазоне [0, size-1]. Например,
     * при наличии в системе 128 «потоков» с номерами от 0 до 127 и 
     * желании независимо измерять каждый из них, можно создать 
     * измеритель с size=128.
     *
     * Нужно указать для измерителя учет пакетов независимо от размера
     * (MeterType.packets) или числа байтов в пакетах (MeterType.bytes).
     */
    meter(bit<32> size, MeterType type);
    /* FIXME - Аргумент size должен иметь тип int, но это нарушает
     * проверку типов
     */

    /***
     * execute_meter() вызывает для указанного состояния считывание, 
     * изменение и запись обратно в виде неделимого блока относительно
     * обработки других пакетов и представление одним из цветов 
     * (green, yellow, red) в параметре out.
     *
     * @param index Индекс обновляемого состояния в массиве. Обычно из
     *              диапазона [0, size-1]. Если index >= size, состояние
     *              не обновляется.
     * @param result Тип T должен быть bit<W> с W >= 2. Если индекс
     *              находится в диапазоне, будет указано значение 0 для
     *              GREEN, 1 - для YELLOW и 2 - для RED (см. RFC 2697 и 
     *              RFC 2698). Если индекс выходит за пределы диапазона,
     *              результат становится неопределенным и его следует
     *              игнорировать вызывающему.
     */
#if V1MODEL_VERSION >= 20200408
    void execute_meter<T>(in I index, out T result);
#else
    void execute_meter<T>(in bit<32> index, out T result);
#endif
}

extern direct_meter<T> {
    /***
     * Объект direct_meter создается вызовом его конструктора. Нужно
     * указать учет пакетов независимо от размера (MeterType.packets)
     * или числа байтов в пакетах (MeterType.bytes). Созданный объект
     * можно связать с одной таблицей, добавляя в ее определение
     * приведенное ниже свойство
     *     meters = <object_name>;
     */
    direct_meter(MeterType type);
    /***
     * После привязки объекта direct_meter к таблице, как описано в 
     * документации конструктора direct_meter, при каждом нахождении
     * в таблице совпадающей записи значение связанного с этой записью
     * измерителя считывается, обновляется и записывается обратно в 
     * форме неделимого блока относительно обработки других пакетов
     * независимо от вызова метода read() из тела данного действия.
     *
     * Метод read() можно вызывать лишь внутри действия, выполняемого в
     * результате совпадения записи в таблице, с которой связан объект 
     * direct_meter. Результатом вызова read() будет численное 
     * представление цвета (green, yellow, red) в параметре out.
     *
     * @param result Тип T должен быть bit<W> с W >= 2. Указывается 
     *              значение 0 для GREEN, 1 - для YELLOW и 2 - для RED 
     *              (см. RFC 2697 и RFC 2698). 
     */
    void read(out T result);
}

#if V1MODEL_VERSION >= 20200408
extern register<T, I>
#else
extern register<T>
#endif
{
    /***
     * Объект register создается вызовом его конструктора. При этом
     * создается массив из size идентичных элементов типа T с индексами
     * из диапазона [0, size-1]. Например, вызов
     *     register<bit<32>>(512) my_reg;
     * выделит место для 512 значений типа bit<32>.
     */
    register(bit<32> size);
    /* FIXME - Аргумент size должен иметь тип int, но это нарушает
     * проверку типов
     */
    /***
     * Метод read() считывает из массива регистров указанное индексом 
     * состояние и возвращает его значение в параметре result.
     *
     * @param index Индекс элемента в массиве регистров для чтения.
     *              Обычно лежит в диапазоне [0, size-1].
     * @param result Поддерживаются лишь типы bit<W>. Когда индекс
     *              находится в диапазоне, result получает значение
     *              указанного элемента массива. Если index >= size, 
     *              результат будет неопределенным и его следует 
     *              игнорировать вызывающему.
     */
    @noSideEffects
#if V1MODEL_VERSION >= 20200408
    void read(out T result, in I index);
#else
    void read(out T result, in bit<32> index);
#endif
    /***
     * Метод write() записывает значение параметра value в массив 
     * регистров по указанному индексу.
     *
     * Если нужно выполнить read(), а затем write() для одного
     * элемента массива регистров и хочется сделать блок 
     * read-modify-write неделимым относительно обработки других пакетов
     * (параллельное выполнение в архитектуре v1model) требуется 
     * указать блок P4_16 с аннотацией @atomic (см. спецификацию P4_16).
     *
     * @param index Индекс элмента массива регистров для записи. Обычно
     *              из диапазона [0, size-1]. Если index >= size, состояние
     *              регистров не изменяется.
     * @param value Поддерживаются лишь типы bit<W>. Когда индекс
     *              находится в диапазоне, значение value записывается
     *              в указанный индексом элемент массива регистров.
     */
#if V1MODEL_VERSION >= 20200408
    void write(in I index, in T value);
#else
    void write(in bit<32> index, in T value);
#endif
}

// Служит атрибутом реализации таблицы
extern action_profile {
    action_profile(bit<32> size);
}

/***
 * Генерирует случайное значение из диапазона [lo..hi] и записывает
 * его в параметр result. Значение result становится неопределенным,
 * если lo > hi.
 *
 * @param T          Должно иметь тип bit<W>
 */
extern void random<T>(out T result, in T lo, in T hi);

/***
 * Вызов digest вызывает передачу сообщения, содержащего значения,
 * указанные в параметре data, программе плоскости управления. Это
 * похоже на отправку клона пакета плоскости управления, но может
 * быть более эффективным за счет того, что сообщения обычно меньше
 * пакета и можно собрать несколько таких сообщений в блок для 
 * одновременной отправки программе плоскости управления.
 *
 * Значениями полей, передаваемых в сообщении плоскости управления, 
 * будут соответствующие значения в момент вызова digest, даже если
 * эти поля позднее будут изменены элементом управления ingress 
 * (см. Примечание 3).
 *
 * Вызов digest поддерживается лишь из элемента управления ingress. 
 * После вызова метода нельзя отказаться от производимого им эффекта.
 *
 * Если тип T является именованной структурой, имя служит для генерации 
 * API плоскости управления.
 *
 * Реализация архитектуры v1model в BMv2 игнорирует значение параметра
 * receiver.
 */
extern void digest<T>(in bit<32> receiver, in T data);

enum HashAlgorithm {
    crc32,
    crc32_custom,
    crc16,
    crc16_custom,
    random,
    identity,
    csum16,
    xor16
}

@deprecated("Please use mark_to_drop(standard_metadata) instead.")
extern void mark_to_drop();

/***
 * mark_to_drop(standard_metadata) является примитивом действия, меняющим 
 * standard_metadata.egress_spec на зависимое от реализации специальное
 * значение, что в некоторых случаях ведет к отбрасывают пакета в конце
 * входной или выходной обработки. Кстанавливается также значение 0 для
 * standard_metadata.mcast_grp. Любое из этих полей метаданных может быть
 * изменено кодом P4 после вызова mark_to_drop(), что может поменять для
 * пакета поведение на отличное от его отбрасывания (drop).
 *
 * В параграфе «Pseudocode for what happens at the end of ingress and 
 * egress processing» документа
 * https://github.com/p4lang/behavioral-model/blob/master/docs/simple_switch.md2 
 * описан относительный приоритет возможных действий применительно к пакету
 * по завершении входной и выходной обработки.
 */
@pure
extern void mark_to_drop(inout standard_metadata_t standard_metadata);

/***
 * Расчет хэш-функции для значения, указанного параметром data. Результат,
 * записываемый в параметр out с именем result, всегда будет находиться в
 * диапазоне [base, base+max-1], если max >= 1. При max=0 будет записано base.
 *
 * Отметим, что типы параметров могут быть одинаковыми или разными и битовые
 * размеры могут различаться.
 *
 * @param O   Должен иметь тип bit<W>
 * @param D   Должен иметь тип tuple, где все поля являются битовыми (bit<W> или int<W>) или varbit.
 * @param T   Должен иметь тип bit<W>
 * @param M   Должен иметь тип bit<W>
 */
@pure
extern void hash<O, T, D, M>(out O result, in HashAlgorithm algo, in T base, in D data, in M max);

extern action_selector {
    action_selector(HashAlgorithm algorithm, bit<32> size, bit<32> outputWidth);
}

enum CloneType {
    I2E,
    E2E
}

@deprecated("Please use verify_checksum/update_checksum instead.")
extern Checksum16 {
    Checksum16();
    bit<16> get<D>(in D data);
}

/***
 * Проверяет контрольную сумму представленных данных. При обнаружении
 * несоответствия поле standard_metadata checksum_error будет иметь 
 * в начале входной обработки значение 1.
 *
 * Вызов verify_checksum поддерживается лишь из элемента VerifyChecksum.
 *
 * @param T          Должен иметь тип tuple, все элементы которого имеют
 *                   тип bit<W>, int<W> или ,varbit<W>. Одщий размер полей
 *                   должен быть кратным выходному размеру.
 * @param O          Тип контрольной суммы (bit<X>).
 * @param condition  Значение false, задает совпадение при любом результате.
 * @param data       Данные для проверки контрольной суммы.
 * @param checksum   Ожидаемая контрольная сумма (должно быть l-value).
 * @param algo       Алгоритм расчета контрольной суммы (могут поддерживаться
 *                   не все алгоритмы), должен быть известной при компиляции
 *                   константой.
 */
extern void verify_checksum<T, O>(in bool condition, in T data, in O checksum, HashAlgorithm algo);

/***
 * Рассчитывает контрльную сумму представленных данных и записывает ее в 
 * параметр checksum.
 *
 * Вызов update_checksum поддерживается лишь из элементов управления 
 * ComputeChecksum.
 *
 * @param T          Должен иметь тип tuple, все элементы которого имеют
 *                   тип bit<W>, int<W> или ,varbit<W>. Одщий размер полей
 *                   должен быть кратным выходному размеру.
 * @param O          Тип контрольной суммы (bit<X>).
 * @param condition  При значении false параметр checksum не изменяется.
 * @param data       Данные для расчета контрольной суммы.
 * @param checksum   Контрольная сумма данных.
 * @param algo       Алгоритм расчета контрольной суммы (могут поддерживаться
 *                   не все алгоритмы), должен быть известной при компиляции
 *                   константой.
 */
@pure
extern void update_checksum<T, O>(in bool condition, in T data, inout O checksum, HashAlgorithm algo);

/***
 * verify_checksum_with_payload идентичен verify_checksum, но учитывает
 * при расчете контрольной суммы данные из пакета (все байты, которые не 
 * разбираются синтаксическим анализатором).
 *
 * Вызов verify_checksum_with_payload поддерживается лишь из элемента
 * управления VerifyChecksum.
 */
extern void verify_checksum_with_payload<T, O>(in bool condition, in T data, in O checksum, HashAlgorithm algo);

/**
 * update_checksum_with_payload идентичен update_checksum, но учитывает
 * при расчете контрольной суммы данные из пакета (все байты, которые не 
 * разбираются синтаксическим анализатором).
 *
 * Вызов update_checksum_with_payload поддерживается лишь из элемента
 * управления ComputeChecksum.
 */
@noSideEffects
extern void update_checksum_with_payload<T, O>(in bool condition, in T data, inout O checksum, HashAlgorithm algo);

/***
 * Вызов resubmit в процессе выполнения элемента ingress будет при 
 * некоторых документированных условиях приводить к повторному 
 * представлению пакета, т. е. пакет будет заново обработан анализатором
 * с тем же содержимым, которое было при предыдущей обработке. Различие
 * состоит лишь в значении поля standard_metadata instance_type и 
 * заданных пользователем полей метаданных, которые операция resubmit 
 * будет сохранять.
 *
 * Значения пользовательских метаданных, сохраняемых при повторном 
 * представлении пакетов, являются значениями в конце входной обработки,
 * а не значениями в момент вызова resubmit (см. Примечание 2).
 *
 * Вызов resubmit поддерживается лишь из элемента управления ingress. 
 * Отменить воздействие этого вызова невозможно. При неоднократном вызове
 * resubmit в одном процессе выполнения элемента ingress повторно 
 * представляется лишь один пакет и сохраняются данные лишь для последнего
 * вызова (см. Примечание 1).
 */
extern void resubmit<T>(in T data);

/***
 * Вызов recirculate в процессе выполнения элемента ingress будет при 
 * некоторых документированных условиях приводить к повторной обработке 
 * пакета, начиная с синтаксического анализа пакета, созданного сборщиком
 * (deparser). Рециркулирующие пакеты могут при входной обработке 
 * различаться с новыми пакетами значением поля standard_metadata
 * instance_type. Вызывающий может запросить сохранение некоторых 
 * полей пользовательских метаданных при рециркуляции пакета.
 *
 * Значения пользовательских метаданных, сохраняемых при рециркуляции 
 * пакетов, являются значениями в конце выходной обработки,
 * а не значениями в момент вызова recirculate (см. Примечание 2).
 *
 * Вызов recirculate поддерживается лишь из элемента управления egress. 
 * Отменить воздействие этого вызова невозможно. При неоднократном вызове
 * recirculate в одном процессе выполнения элемента egress рециркулирует 
 * лишь один пакет и сохраняются данные лишь для последнего
 * вызова (см. Примечание 1).
 */
extern void recirculate<T>(in T data);

/***
 * Операция clone почти идентична clone3 и отличается лишь тем, что
 * никогда не сохраняет поля пользовательских метаданных в клонах пакетов.
 * Она эквивалентна вызову clone3 с теми же параметрами type и session, но
 * с пустым параметром data.
 */
extern void clone(in CloneType type, in bit<32> session);

/***
 * Вызов clone3 при выполнении элемента управления ingress или egress
 * приводит к созданию клонов пакета (иногда называют отражением). 
 * Т. е. могут создаваться копии пакета, каждая из которых затем 
 * подвергается выходной обработке как независимый пакет. Для исходного
 * пакета продолжается обычная обработка независимо от клонов.
 *
 * Целочисленный параметр session содержит идентификатор сеанса клонирования
 * (иногда называется идентификатором сеанса отражения). Программы плоскости
 * управления должны настроить конфигурацию каждой сессии, которую планируется
 * использовать. Без этого клонирования происходить не будет. Обычно такая 
 * настройка включает задание плоскостью управления выходного порта, куда 
 * будет передаваться клон или списка пар (port, egress_rid), для которых 
 * следует создавать клоны подобно групповым пакетам.
 *
 * Клонированные пакеты можно различать по значениям поля 
 * standard_metadata instance_type.
 *
 * Вызывающий может запросить сохранение некоторых полей пользовательских 
 * метаданных исходного пакета при клонировании. Сохраняться будут значения
 * в конце входной или выходной обработки, а не в момент вызова clone3
 * (см. Примечание 2).
 *
 * При вызове clone3 во время входной обработки первым параметром должен быть
 * CloneType.I2E, для вызова при выходной обработке - CloneType.E2E.
 *
 * Отменить воздействие этого вызова невозможно. При неоднократном вызове
 * clone3 и/или clone в одном процессе входной или выходной обработки 
 * используется лишь последняя сессия клонирования и данные (см. Примечание 1).
 */
extern void clone3<T>(in CloneType type, in bit<32> session, in T data);

extern void truncate(in bit<32> length);

/***
 * Вызов assert с аргументом, имеющим значение true не дает эффекта, за 
 * исключением тех, которые может давать вычисление аргумента (см. ниже).
 * Если аргумент имеет значение false, поведение зависит от целевой платформы,
 * но цель заключается в записи или выводе информации об отказавшем операторе 
 * assert и возможно дополнительных данных об отказе.
 *
 * Например, на платформе simple_switch выполнение оператора assert с
 * аргументом false вызывает сообщение с именем файла и номером строки с
 * оператором assert, после чего процесс simple_switch завершается.
 *
 * Если была задана опция --ndebug в командной строке p4c при компиляции,
 * скомпилированная программа ведет себя как при отсутствии операторов 
 * assert в исходном коде.
 *
 * Настоятельно рекомендуется избегать выражений в качестве аргумента 
 * assert, поскольку это может приводить к побочным эффектам, например,
 * вызову внешнего метода или функции с побочным эффектом. Компилятор p4c 
 * позволяет это, не выдавая предупреждений. Это рекомендуется потому, что
 * программа будет вести себя так же, как при удалении операторов assert.
 */
extern void assert(in bool check);

/***
 * Для целей компиляции и запуска программ P4 на целевом устройстве
 * операторы assert и assume являются идентичными, включая использование
 * опции --ndebug компилятор p4c для их исключения. См. описание assert.
 *
 * Причиной использования assume как отдельной функции является допущение 
 * о ее специальной трактовке инструментами формальной проверки. Для 
 * некоторых таких инструментов цель состоит в попытке найти примеры 
 * пакетов и установленных записей таблиц, где условие оператора assert
 * имеет значение false.
 *
 * Предположим, что такой инструмент запускается для программы и примером 
 * служит пакет MPLS, где hdr.ethernet.etherType == 0x8847. При рассмотрении
 * примера видно, что условие assert имеет значение false. Однако планируется 
 * развертывание программы P4 в сети, где нет пакетов MPLS. Можно добавить
 * в программу P4 дополнительные условия для корректной обработки пакетов без
 * отказов assert, но проще сказать инструменту, что «такие экземпляры пакетов
 * не применимы и не нужно их показывать» путем добавления оператора
 *     assume(hdr.ethernet.etherType != 0x8847);
 * в подходящее место программы. Тогда формальный инструмент не будет показывать 
 * такие примеры, ограничиваясь лишь теми, которые дают условие true.
 *
 * Причина одинакового поведения операторов assume и assert при компиляции для
 * целевого устройства заключается в том, что значение false при оценке 
 * условия во время работы в сети может говорить об ошибочности допущения 
 * и необходимости его проверки.
 */
extern void assume(in bool check);

/*
 * Вывод заданного пользователем сообщения.
 * Например, log_msg("User defined message");
 * или log_msg("Value1 = {}, Value2 = {}",{value1, value2});
 */
extern void log_msg(string msg);
extern void log_msg<T>(string msg, in T data);

// Имя standard_metadata является зарезервированным.

/*
 * Архитектура.
 *
 * M должно иметь тип struct.
 *
 * H должно иметь тип struct, где каждый из элементов является заголовком,
 * стеком или объединением заголовков.
 */

parser Parser<H, M>(packet_in b,
                    out H parsedHdr,
                    inout M meta,
                    inout standard_metadata_t standard_metadata);

/*
 * В теле элемента управления VerifyChecksum допускаются лишь операторы 
 * блока, вызовы методов verify_checksum и verify_checksum_with_payload, 
 * а также операторы return.
 */
control VerifyChecksum<H, M>(inout H hdr,
                             inout M meta);
@pipeline
control Ingress<H, M>(inout H hdr,
                      inout M meta,
                      inout standard_metadata_t standard_metadata);
@pipeline
control Egress<H, M>(inout H hdr,
                     inout M meta,
                     inout standard_metadata_t standard_metadata);

/*
 * В теле элемента управления ComputeChecksum допускаются лишь операторы 
 * блока, вызовы методов update_checksum и update_checksum_with_payload, 
 * а также операторы return.
 */
control ComputeChecksum<H, M>(inout H hdr,
                              inout M meta);

/*
 * В теле элемента управления Deparser допускается лишь метод packet_out.emit().
 */
@deparser
control Deparser<H>(packet_out b, in H hdr);

package V1Switch<H, M>(Parser<H, M> p,
                       VerifyChecksum<H, M> vr,
                       Ingress<H, M> ig,
                       Egress<H, M> eg,
                       ComputeChecksum<H, M> ck,
                       Deparser<H> dep
                       );

#endif  /* _V1_MODEL_P4_ */

Николай Малых

nmalykh@protocols.ru

1Имеется перевод документа на русский язык.

2Параграф «Псевдокод для завершения входной и выходной обработки» в переводе.




Параметры simple_switch

PDF

Синтаксис

simple_switch [опции] <путь к JSON-файлу конфигурации коммутатора>

Опции

-h [ —help ]

Выводит справочную информацию и на этом завершает работу.

-i [ —interface ] arg <port-num>@<interface-name>

Привязывает сетевой интерфейс <interface-name> в качестве порта с номером <port-num> при запуске программы. Опция может использоваться в команде многократно.

—pcap [=arg(=.)]

Задает создание файлов pcap для интерфейсов коммутатора. Необязательный аргумент позволяет указать каталог для сохранения файлов pcap, которые по умолчанию записываются в текущий каталог.

—use-files arg

Считывает пакеты из файлов или записывает в файлы (интерфейсу X соответствуют файлы X_in.pcap и X_out.pcap). Аргумент задает время ожидания (в секундах) перед началом обработки файлов с пакетами.

—packet-in arg

Включает прием пакетов на этом (nanomsg) сокете. В этом случае опция —interface будет игнорироваться.

—thrift-port arg

Порт TCP на котором будет работать сервер интерфейса управления Thrift.

—device-id arg

Идентификатор, указывающий устройство в сообщениях IPC. По умолчанию 0.

—nanolog arg

Сокет IPC для использования с журналами nanomsg pub/sub. По умолчанию журналы nanomsg не ведутся.

—log-console

Включает вывод журнальной информации на устройство stdout.

—log-file arg

Включает вывод журнала в указанный файл.

-L [ —log-level ] arg

Задает уровень детализации при выводе журнала работы и может принимать значение trace, debug, info, warn, error или off. По умолчанию используется уровень trace.

—log-flush

При использовании с опцией —log-file задает сброс журнала на диск после каждого сообщения.

—notifications-addr arg

Указывает адрес nanomsg для уведомлений (обучение, старения и т. п.), по умолчанию ipc:///tmp/bmv2-<device-id>-notifications.ipc

—restore-state arg

Задает восстановление состояния из файла.

—dump-packet-data arg

Задает число байтов для дампа принимаемых и передаваемых пакетов. Дамп выводится с уровнем info, поэтому следует убедиться, что при выводе этот уровень не исключается. По умолчанию установлено значение 0 и не выводится ничего.

-v [ —version ]

Выводит информацию о версии программы.

—json-version

Выводит максимальную поддерживаемую версию bmv2 JSON в формате <major>.<minor>. Должны поддерживаться все версии bmv2 JSON с совпадающим значением <major>.

—no-p4

Позволяет запустить коммутатор без файла конфигурации.

—max-port-count arg (=512)

Максимальное число интерфейсов, которые можно привязать к коммутатору. Это не верхняя граница номера порта, который может быть произвольным. В зависимости от платформы максимальное значение может не применяться.

Платформа имеет также свой анализатор команд и нужно отделять опции платформы от опций bmv2 двумя дефисами (—).

Опции платформы

—load-modules arg

Загружает указанные через запятую файлы .so как модули. Это полезно при использовании динамических библиотек с реализациями типа extern.

—enable-swap

Включает файл подкачки JSON в процессе работы.

—drop-port arg

Задает номер порта drop (по умолчанию 511).




Компилятор P4C (драйвер компиляции)

PDF

Синтаксис

p4c [-h] [-V] [-v] [-###] [-Xpreprocessor <arg>] [-Xp4c <arg>] 
    [-Xassembler <arg>] [-Xlinker <arg>] [-b TARGET] [-a ARCH] [-c] 
    [-D PREPROCESSOR_DEFINES] [-E] [-e] [-g] [-I SEARCH_PATH] [-o PATH] 
    [--p4runtime-file P4RUNTIME_FILE] 
    [--p4runtime-files P4RUNTIME_FILES] 
    [--p4runtime-format {binary,json,text}] [--help-pragmas] 
    [--help-targets] [--disable-annotations DISABLED_ANNOS] [-S] 
    [--std {p4-14,p4_14,p4-16,p4_16}] [--ndebug] 
    [source_file]

Позиционные аргументы source_file обязательны (хотя бы один) и указывают имена файлов P4 для компиляции.

Опции

Остальные аргументы команды являются необязательными (опции) и служат для управления компиляцией исходного кода P4. Многие опции доступны в краткой и длинной форме (с префиксом —)

-h, —help

Выводит справку о параметрах команды и завершает работу.

-V, —version

Выводит номер версии программы и завершает работу.

-v, —debug

Задает подробный вывод программы.

-###, —test-only

Выводит команды на консоль без из реального выполнения.

-Xpreprocessor <arg>

Задает передачу аргумента <arg> препроцессору компилятора.

-Xp4c <arg>

Задает аргумент, передаваемый компилятору.

-Xassembler <arg>

Задает передачу аргумента <arg> ассемблеру.

-Xlinker <arg>

Задает передачу аргумента <arg> компоновщику.

-b TARGET, —target TARGET

Задает целевое устройство (платформу).

-a ARCH, —arch ARCH

Задает целевую архитектуру.

-c, —compile

Задает выполнение лишь этапов предварительной обработки, компиляции и сборки.

-D PREPROCESSOR_DEFINES

Определяет макрос, используемый препроцессором.

-E

Задает выполнение только препроцессора.

-e

Задает пропуск препроцессора.

-g

Задает генерацию отладочной информации.

-I SEARCH_PATH

Задает каталог для поиска включаемых файлов.

-o PATH, —output PATH

Задает каталог для записи вывода.

—p4runtime-file P4RUNTIME_FILE

Задает вывод описания P4Runtime (API плоскости управления) в указанный файл. Опция устарела и взамен следует использовать —p4runtime-files.

—p4runtime-files P4RUNTIME_FILES

Задает вывод описания P4Runtime (P4Info, API плоскости управления) в указанные через запятые файлы. Формат файлов задают указанные расширения имен (суффиксы) .txt, .json, .bin.

—p4runtime-format {binary,json,text}

Задает выходной формат для описания P4Runtime API (по умолчанию .bin). Опция устарела и взамен следует использовать —p4runtime-files.

—help-pragmas, —pragma-help, —pragmas-help, —help-annotations, —annotation-help, —annotations-help

Выводит документацию поддерживаемых annotation и pragma, после чего завершает работу.

—help-targets, —target-help, —targets-help

Выводит связанные с целевой платформой опции команд.

—disable-annotations DISABLED_ANNOS, —disable-annotation DISABLED_ANNOS …

Задает список разделенных запятыми аннотаций, которые компилятору следует игнорировать.

-S

Задает выполнение лишь этапов предварительной обработки и компиляции.

—std {p4-14,p4_14,p4-16,p4_16}, -x {p4-14,p4_14,p4-16,p4_16}

Указывает вариант языка во входных файлах.

—ndebug

Задает компиляцию программы без отладочной информации.

Николай Малых

nmalykh@protocols.ru




Промежуточные представления компилятора P4C

Оригинал

PDF

Введение

В этом документе рассматривается устройство компилятора P416, описаны разные классы и структуры данных, применяемые для компиляции, а также сам процесс компиляции. Компилятор рассчитан на «традиционную» модель работы, преобразующую программу P4 во внутреннее представление, выполняющую несколько проходов с этим представлением, дающих в результате промежуточное представление (IR1), которое более или менее напрямую отображается в код для целевой платформы. По ходу дела набор классов IR, представленных в коде, может меняться, при этом некоторые конструкции будут присутствовать лишь в начальном представлении после синтаксического анализатора, а другие будут добавляться в процессе компиляции.

Новизна заключается в возможности всегда вернуться к более ранней версии IR (обратное прохождение) или использовать множество потоков при работе с IR, что позволяет реализовать разные стратегии при поиске окончательного результата. Поддержка ветвления и возврата является центральным моментом IR и преобразования. Задачей IR является представление программы в виде дерева или DAG2 с одним корневым узлом и остальными узлами, указывающими лишь своих потомков. Преобразования выполняются путем создания новых узлов IR, являющихся «мелкими» копиями имеющихся узлов, и последующего создания для них новых родительских узлов вплоть до корня IR. Внутреннее представления реализовано на C++ в форме одной иерархии наследования объектов с основным базовым узлом типа Node, используемым всеми субклассами. Этот базовый класс Node поддерживает функциональность, требуемую для преобразований — клонирование, проверку эквивалентности, диспетчеризацию субклассов.

Представление IR должно быть расширяемым, что позволяет добавлять новые классы в иерархию по мере добавления поддержки новых платформ. Исходное представление IR создаваемое синтаксическим анализатором, содержит конструкции, относящиеся лишь к исходному коду P4, и дле него будут выполняться преобразования frontend-компилятором для перевода в более простую и каноническую форму.

IR может быть деревом или DAG, но разрешение ссылок «вперед-назад» (back/up) в IR (циклах) сводит на нет большинство преимуществ неизменного IR. Если циклы (например, узлы-листья для имен напрямую ссылающиеся на именованный объект) разрешены, практически любое преобразование будет приводить к клонированию всего графа IR, поскольку изменение листа включает клонирование содержащего его объекта, а затем (рекурсивно) любого объекта, указывающего на этот объект.

Проходы Visitor и Transform

Компилятор организован в форме серии проходов Visitor и Transform. Базовые классы Visitor и Transform упрощают определение новых проходов и новому преобразованию требуется лишь указать нужные типы IR, а прочие можно игнорировать. Проход Visitor (постоянный) посещает каждый узел в дереве IR м собирает информацию о дереве, не меняя ее, а проход Transform посещает каждый узел и может менять его или заменять иным узлом.

    /* псевдокод базового преобразования */
    visit(node, context=ROOT, visited={}) {
        if (node in visited) {
                if (visited[node] == INVALID)
                        throw loop_detected
                if (VisitDagOnce)
                        return visited[node] }
        visited[node] = INVALID
        copy = shallow_clone(node)
        if (VisitDagOnce)
                forward_children(copy, visited)
        copy = preorder(copy, context) // переопределен в субклассе Transform
        visit_children(copy, {copy, context}, visited)
        copy = postorder(copy, context) // переопределен в субклассе Transform
        if (*copy == *node) {
                visited[node] = node
                return node
        } else {
                visited[node] = copy
                return copy }
    }
    forward_children(node, visited) {
        for (child in children(node))
                if (child in visited)
                        child = visited[child]
    }
    visit_children(node, context, visited) {
        for (child in children(node))
                child = visit(child, context, visited)
    }

В проходе Transform разрешено свободно изменять или полностью заменять узел в своих программах и будет автоматически перестраивать дерево должным образом. Подпрограммы преобразования не могут менять другие узлы в дереве, однако они могут обращаться к ним и ссылаться на них. Эти программы могут быть перезагружены, чтобы автоматически различать разные субклассы IR. Любому данному посетителю узла нужно лишь переопределить интересующие программы для нужных типов IR.

Имеется несколько субклассов Visitor, описывающих разные типы посетителей.

Субкласс

Описание

Inspector

Простой посетитель, собирающий информацию, но не меняющий узлы.

Modifier

Простой посетитель, которые не меняет структуру дерева, но может менять узлы.

Transform

Полное преобразование узла

PassManager

Комбинация нескольких типов, выполняемых последовательно.

Имеется также два интерфейса, которые могут реализовать субклассы Visitor для изменения режима посещения узлов.

Интерфейс

Описание

ControlFlowVisitor

Посещение узлов в порядке потока управления, расщепление (клонирование) посетителя по условию и слияние клонов в точке соединения.

Backtrack

Посетитель уведомляется, когда происходит возврат, и может сохранить свое состояние как точку возврата.

ControlFlowVisitor

Для поддержки анализа потока управления имеется специальный интерфейс ControlFlowVisitor,
который
понимают определенные субклассы
IR, инкапсулирующие поток управления, и применяют для управления порядком посещения узлов-потомков и состоянием посетителя, передаваемым его подпрограммам preorder/postorder для анализа потока управления.

Базовая идея заключается в том, что узлы потока управления в IR при посещении своих потомков создают клоны объектов ControlFlowVisitor для посещения различных потомков после ветвления и вызывают функцию flow_merge в точке соединения для слияния состояний посетителя. Базовый класс Visitor и интерфейс ControlFlowVisitor определяют две виртуальные функции — flow_clone и flow_merge решающие эти задачи. В базовом классе Visitor (используется всеми посетителями, не наследующими из интерфейса ControlFlowVisitor), где функции реализованы в виде no-ops, функция flow_clone просто возвращает тот же объект, а flow_merge не делает ничего. В интерфейсе ControlFlowVisitor функция flow_clone вызывает функцию clone (которая должна быть реализована классом leaf), а flow_merge является абстрактной виртуальной функцией, которая должна быть реализована в классе leaf.

В качестве примера использования в программах посетителя IR рассмотрим класс IR::If, имеющих 3 дочерних элемента для посещения — утверждение (predicate) и два следствия (consequent), причем значение утверждения определяет, какое из следствий выполняется. Дочерний посетитель для IR::If имеет вид

    visitor.visit(predicate);
    clone = visitor.flow_clone();
    visitor.visit(ifTrue);
    clone.visit(ifFalse);
    visitor.flow_merge(clone);

Таким образом оба следствия посещаются с состоянием посетителя, соответствующим потоку управления, сразу после оценки утверждения, а затем состояния потока управления после обоих посещений объединяются.

Для использования этой функциональности субклассу Visitor нужно лишь наследовать от интерфейса ControlFlowVisitor и реализовать функции clone и flow_merge. Другим классам Visitor не нужно делать ничего, но они могут реализовать clone по иным причинам.

Этот анализ потока управления работает лишь с потоком управления «ветвление-слияние» и не подходит для циклов (которые обычно требуют итерации процесса до фиксированной точки). Поскольку IR (в настоящее время) не поддерживает циклы, это не создает проблем. Язык P4 не разрешает включать циклы в таблицы «сопоставление-действие» внутри конвейера и это работает достаточно хорошо, но может быть недостаточно для синтаксических анализаторов, которые могут поддерживать циклы.

Поток в целом

Поток компиляции можно грубо разделить на три части — frontend, mid-end и backend. Интерфейсная часть (frontend) выполняет в основном не связанные с целевой платформой преобразования, предназначенные для очистки IR и приведения в каноническую форму. Средняя часть (middleend) выполняет зависящие от целевой платформы преобразования IR. Последняя часть (backend) принимает решения о выделении ресурсов. Поскольку этот этап может завершаться отказом по причине имеющихся ограничений, может потребоваться возврат к mid-end для проверки иной конфигурации. Возврата к начальному этапу (frontend) не предполагается. Разделение на этапы Front/Middle/Back достаточно произвольно и некоторые проходы могут перемещаться между этапами, если в этом будет смысл.

Frontend

Интерфейсная часть (frontend) анализирует исходный код и преобразует его в представление IR, напрямую соответствующее исходному коду. Затем выполняется серия проходов, таких как проверка типов (изменение узлов для указания выведенных типов), раскрытие констант, устранение неиспользуемого кода и т. п. Дерево первоначального IR соответствует исходному коду, а затем выполняются различные (в основном независимые от целевой платформы) преобразования. Сложная структура и сложные области действия в коде здесь преобразуются в «плоское» представление.

Mid-end

На этапе mid-end выполняются преобразования, в той или иной степени зависящие от целевой платформы, но не связанные с выделением конкретных ресурсов. В какой-то части этих преобразований представление IR преобразуется в фрому, соответствующую возможностям целевой платформы.

Управление проходами

Для управления возвратами и порядком проходов и преобразования служит объект PassManager, который инкапсулирует последовательность проходов и выполняет их по порядку. Проходы, поддерживающие интерфейс Backtrack, являются точками возврата.

Базовый объект PassManager может управлять динамически создаваемыми последовательностями проходов. Он отслеживает проходы, реализующие Backtrack, и при обнаружении отказа на последующем этапе возвращается к проходу Backtrack.

    interface Backtrack {        bool backtrack(trigger);     }

Метод backtrack вызывается при возникновении необходимости возврата и возвращает значение true, если в повторном проходе можно проверить применимый вариант. В противном случае (вариантов нет) возвращается false для поиска иной точки возврата.

Использование исключений

Исключения применимы при ошибках, о которых можно сообщить пользователю, проблемах с ограничениями, требующих возврата, и внутренних проблемах компилятора. Для этого создано множество классов исключений.

  • Backtrack::trigger служит для управления возвратом и может включать субклассы для добавления информации.

  • CompilerBug используется при внутренних ошибках компилятора. Взамен непосредственного вызова служит макрос BUG().

Классы IR

Чтобы упростить управление и расширений для классов IR, они определяются файлах .def, которые обрабатываются программой ir-generator, создающей код IR C++. По сути, файлы .def являются просто определениями классов C++ с удаленным шаблоном, который вставляет программа ir-generator, расщепляя также определение каждого класса на файлы .h и .cpp по мере необходимости.

Основная часть «нормального» IR определяется в каталоге ir, при этом некоторые frontend и backend используют свои расширения имен для IR, определяемых в их подкаталогах внутри ir. Программа ir-generator считывает все файлы .def в дереве и создает по одному файлу .h и .cpp, которые содержат весь код определения класса IR.

Все ссылки на объекты класса IR из объектов других классов IR должны быть указателями const, чтобы поддерживать инварианты, открытые лишь для записи (write-only). Когда это слишком обременительно, можно встраивать классы IR непосредственно в другие объекты IR, вместо использования ссылок. При таком встраивании объектов IR они становятся доступными для изменения посетителями Modifier
и
Transform при посещении содержащего объект узла, а сами объекты непосредственно посещаться не будут.

Программа ir-generator понимает оба эти механизма. Любое поле, объявленное в классе IR с другим классом IR в качестве типа, будет преобразовано в указатель const, если у него нет встроенного модификатора (в данном случае это будет встроенный напрямую субобъект).

Программа ir-generator понимает множество «стандартных» методов для классов IR — visit_children, operator==, dbprint, toString, apply. Эти методы можно объявлять без аргументов и типов возврата (т. е. просто указывать имя метода с блоком кода в фигурных скобках — {}), при этом будет синтезировано стандартное объявление. Если метод не объявлен в файле .def, создается стандартное определение (наоснове объявленных в классе полей). Таким способом большинство классов могут избежать включения этого шаблонного кода в файл .def.

IR::Node

Это основной абстрактный класс для всех узлов IR, содержащий лишь небольшое количество данных для информирования об ошибках и отладки. В общем случае эти данные никогда не сравниваются на предмет равенства (субклассам никогда не следует вызывать Node::operator==`), поскольку узлы (Node), различающиеся лишь этой информацией следует считать эквивалентными и не требовать для них клонирования или дерева IR.

IR::Vector<T>

Этот шаблонный класс содержит вектор указателей (const) на узлы конкретного субкласса IR::Node.

Перевод на русский язык

Николай Малых

nmalykh@protocols.ru

1Intermediate representation.

2Directed acyclic graph — ориентированный ациклический граф (орграф), в котором отсутствуют направленные циклы, но могут быть «параллельные» пути, выходящие из одного узла и разными путями приходящие в конечный узел.




RFC 8875 Working Group GitHub Administration

Internet Engineering Task Force (IETF)                         A. Cooper
Request for Comments: 8875                                         Cisco
Category: Informational                                       P. Hoffman
ISSN: 2070-1721                                                    ICANN
                                                             August 2020

Working Group GitHub Administration

Администрирование рабочих групп GitHub

PDF

Аннотация

Использование GitHub в рабочих группах (WG) IETF возрастает. Этот документ описывает использование и соглашения для рабочих групп, начинающих пользоваться GitHub. Документ не задает обязательных процессов и не требует изменения работы существующих и будущих групп, которые не пользуются GitHub.

Статус документа

Документ не содержит какой-либо спецификации (Internet Standards Track) и публикуется с информационными целями.

Документ является результатом работы IETF1 и представляет согласованный взгляд сообщества IETF. Документ прошел открытое обсуждение и был одобрен для публикации IESG2. Дополнительную информацию о стандартах Internet можно найти в разделе 2 в RFC 7841.

Информация о текущем статусе документа, найденных ошибках и способах обратной связи доступна по ссылке https://www.rfc-editor.org/info/rfc8875.

Авторские права

Copyright (c) 2020. Авторские права принадлежат IETF Trust и лицам, указанным в качестве авторов документа. Все права защищены.

Документ является субъектом прав и ограничений, указанных в BCP 78 и IETF Trust Legal Provisions и относящихся к документам IETF (http://trustee.ietf.org/license-info), на момент публикации данного документа. Прочтите упомянутые документы внимательно, поскольку в них описаны права и ограничения, относящиеся к данному документу. Фрагменты программного кода, включенные в этот документ, распространяются в соответствии с упрощенной лицензией BSD, как указано в параграфе 4.e документа IETF Trust Legal Provisions, без каких-либо гарантий (как указано в Simplified BSD License).

Оглавление

Исключено в варианте HTML.

1. Введение

Многие рабочие группы и участники процессов IETF используют GitHub разными способами в своей работе над документами IETF. Некоторые люди заинтересованы в использовании GitHub их рабочими группами, но не знают, с чего начать или каким соглашениям следовать. Некоторые рабочие группы используют или планируют применять другие репозитории кода, такие как GitLab и Bitbucket, свойства которых отличаются от GitHub.

В этом документе задан набор административных процессов и соглашений для рабочих групп IETF, которые следует применять при выборе GitHub для упрощения своей работы. Эти спецификации не предназначены для рабочих групп и отдельных лиц, которые уже применяют GitHub для работы IETF. Практика таких групп различается и некоторые из них не соответствуют предлагаемым здесь соглашениям (это нормально). Цель спецификации не состоит в формировании единообразной практики и основной задачей является помощь рабочим группам, начинающим использовать GitHub, описав проверенные подходы способы, которые можно перенять при желании.

2. Административный процесс и соглашения

В этом разделе описан административный процесс и соглашения для поддержки создания и управления организацией в GitHub для рабочих групп и хранилищ отдельных документов единообразным способом. Эти действия можно выполнить вручную через секретариат IETF или автоматизированным путем. Примеры автоматизированных решений доступны по ссылкам https://github.com/richsalz/ietf-gh-scripts и https://github.com/martinthomson/i-d-template.

В этом документе вопрос выбора ручного или автоматизированного процесса намеренно не рассматривается, поскольку эти детали будут рассмотрены в IETF Secretariat и Tools Team.

Большинство приведенных ниже соглашений взяты из [RFC8874].

2.1. Создание организации в GitHub

Этот документ указывает, что интерфейс IETF Datatracker позволяет руководителям направлений (area director — AD) или рабочих групп запрашивать создание в GitHub организации для конкретной рабочей группы. В идеале эта возможность должна присутствовать как часть пользовательского интерфейса (UI) создания рабочей группы и ее страницы.

При запросе руководителем направления или рабочей группы создания организации в GitHub инициируется описанный ниже процесс.

  1. Для рабочей группы создается организация GitHub.

  2. Имя организации имеет формат ietf-wg-<wgname>…

  3. Организация инициализируется путем указания IETF Secretariat и руководителей направления в качестве владельцев области данной группы. Если ответственный AD для рабочей группы работает в другом направлении (area), этот AD также будет владельцем.

  4. Организация инициализируется с указанием администраторов, в число которых входят руководитель и секретарь рабочей группы, если они есть.

После создания организации ее URL добавляется на страницу рабочей группы в Datatracker.

Этапы 3 и 4 подразумевают, что идентификаторы владельцев и администраторов известны GitHub. Запись идентификаторов GitHub в Datatracker (см. https://trac.tools.ietf.org/tools/ietfdb/ticket/2548) будет облегчать это. Лицо, запрашивающее создание организации о недоступности идентификаторов GitHub для любого из указанных в качестве владельцев и администраторов.

2.2. Перенос существующих организаций

Если рабочая группа уже имеет организацию, для нее будет полезно указание такого же персонала управления, как описано в параграфе 2.1. Т. е. будет полезно иметь возможность выполнить этапы 3 и 4 из параграфа 2.1, чтобы описанные ниже действия, такие как смена персонала, выполнялись так же, как для созданной заново организации.

2.3. Изменение персонала

При смене персонала для направления или рабочей группы эти изменения будут отражаться в организации GitHub. В Datatracker следует обеспечить возможность отслеживания смены персонала.

2.4. Закрытие рабочей группы

При закрытии рабочей группы команда с административным доступом удаляется, а список владельцев возвращается в секретариат и текущим AD (на момент закрытия). Сводные данные об организации и репозитории внутри организации обновляются для указания того, что работа завершена. Позже список владельцев может быть ограничен секретариатом или включать других лиц по выбору секретариата или IESG.

2.5. Создание репозитория документов

Имеется много вариантов и конфигураций, в которых может быть полезно использование автоматизации или административных соглашения для репозиториев в рамках организации WG:

  • создание нового репозитория для отдельного чернового документа (по решению руководителя WG);

  • создание нового репозитория для уже принятого рабочей группой проекта (draft);

  • перенос имеющегося репозиория документов в организацию WG;

  • создание нового репозитория с множеством черновых документов.

В качестве этапа процесса этот документ указывает наличие в интерфейсе Datatracker инструмента, позволяющего администратору организации ietf-wg-<wgname> запросить создание нового репозитория для одного документа внутри этой организации. Авторы этого документа будут указаны как участники работы, а репозиторий получит имя чернового документа. В идеале репозиторий должен быть настроен с шаблоном чернового документа, файлами CONTRIBUTING, LICENSE и README, а также с поддержкой непрерывной интеграции в стиле https://github.com/martinthomson/i-d-template. Выполнение этого этапа будет автоматически информировать IETF Secretariat о необходимости создать резерную копию репозитория в соответствии с параграфом 3.2.

2.6. Список связанных репозиториев

В IETF Datatracker следует позволять пользователя добавлять ссылки на репозитории (GitHub и др.) для рабочих групп, документов и страниц пользователей. В данный момент эта функция находится стадии разработки.

3. Рабочий процесс

В [RFC8874] рассмотрены разные варианты использования GitHub рабочими группами и множество решений для реализации этого. В этом разделе указаны базовые административные правила для рабочих групп и описана административная поддержка этих правил.

3.1. Участники работы

Каждый репозиторий, созданный в организации WG, должен как минимум включать в свой файл CONTRIBUTING шаблонный текст https://trustee.ietf.org/license-for-open-source-repositories.html из файла лицензии IETF для репозиториев с открытым кодом. Этот файл может включать и другую информацию (см., например, https://github.com/ietf/repo-files/tree/master/contributing-samples).

Будет полезно указывать в пользовательских данных Datatracker как минимум список учетные данные пользователей GitHub, чтобы упростить отслеживание их вклада в работу.

Некоторые рабочие группы предпочитают иметь в репозитории несколько черновых вариантов, особенно для документов, связанных перекрестными ссыоками. В таких случаях в файле README этого репозитория следует четко указывать это, чтобы читатель знал о возможности одновременного изменения нескольких черновых документов.

3.2. Резервные копии и архивы содержимого GitHub

Списки рассылок рабочих групп IETF автоматически копируются секретариатом IETF и архивы доступны для всех. Все официальные взаимодействия в WG должны архивироваться.

Информация рабочей группы в GitHub также должна архивироваться с обеспечением публичного доступа к архивам. Этот документ задает использование для решения этих задач протокола Git [git-protocol].

Репозиторий каждой рабочей группы IETF в GitHub будет иметь одноименный репозиторий (зеркало) на сервере, поддерживаемом секретариатом IETF. Каждый час служба будет использовать команду git fetch для всех отслеживаемых репозиториев GitHub. Зеркало репозитория будет обеспечивать доступ для всех.

Отметим, что эта система не делает резервных копий и запросов на вытягивание (pull). Резервные копии следует делать и GitHub API позволяет это. Секретариату IETF следует делать резерные копии зеркала одновременно с созданием резерных копий репозиториев GitHub.

Шаги, описанные в параграфе 2.5, информируют IETF Secretariat о репозиториях, для которых следует создать резервные копии. Руководителям рабочих групп и направлений следует аткже иметь возможность запрашивать у секретариата IETF резервное копирование других репозиториев и связанных рабочих IETF.

4. Вопросы безопасности

Злоумышленник может изменить содержимое Internet-Drafts, особенно на финальных этапах работы и внести незаметные изменения в протоколы, которые могут быть в коненом итоге приняты.

Существует риск потери данных из-за их размещения в одном сервисе. Это учитывается и может быть смягчено мерами, описанными в параграфе 3.2.

5. Взаимодействие с IANA

Этот документ не требует действий IANA.

6. Литература

[git-protocol] Chacon, S. and B. Straub, «Git on the Server — The Protocols», in Pro Git, 2014, <https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols#The-Git-Protocol>.

[RFC8874] Thomson, M. and B. Stark, «Working Group GitHub Usage Guidance», RFC 8874, DOI 10.17487/RFC8874, August 2020, <https://www.rfc-editor.org/info/rfc8874>.

Адреса авторов

Alissa Cooper

Cisco

Email: alcoop@cisco.com

Paul Hoffman

ICANN

Email: paul.hoffman@icann.org

Перевод на русский язык

Николай Малых

nmalykh@protocols.ru

1Internet Engineering Task Force.

2Internet Engineering Steering Group.