diff --git a/.github/buildomat/jobs/build-and-test.sh b/.github/buildomat/jobs/build-and-test.sh index d0331ea4..ded0584b 100755 --- a/.github/buildomat/jobs/build-and-test.sh +++ b/.github/buildomat/jobs/build-and-test.sh @@ -8,6 +8,9 @@ #: "/work/debug/*", #: "/work/release/*", #: ] +#: access_repos = [ +#: "oxidecomputer/htq", +#: ] #: set -o errexit diff --git a/.gitignore b/.gitignore index e05bdad3..4cd416b4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.sw* out.rs tags +!.github diff --git a/Cargo.lock b/Cargo.lock index 6d34545a..2d6eabb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -288,6 +288,14 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "htq" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/htq?branch=sector-001#cc2e6230b13d6d9d3f5edb55201e4a93314503bb" +dependencies = [ + "winnow", +] + [[package]] name = "ipnetwork" version = "0.20.0" @@ -449,6 +457,23 @@ dependencies = [ "regex", ] +[[package]] +name = "p4-cg" +version = "0.1.0" +dependencies = [ + "p4", + "thiserror 1.0.69", +] + +[[package]] +name = "p4-htq" +version = "0.1.0" +dependencies = [ + "htq", + "p4", + "thiserror 1.0.69", +] + [[package]] name = "p4-macro" version = "0.1.0" @@ -477,6 +502,7 @@ name = "p4-rust" version = "0.1.0" dependencies = [ "p4", + "p4-cg", "prettyplease", "proc-macro2", "quote", @@ -1158,6 +1184,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.6.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen-rt" version = "0.39.0" @@ -1184,6 +1219,7 @@ dependencies = [ "clap", "colored", "p4", + "p4-htq", "p4-rust", "regex", ] diff --git a/Cargo.toml b/Cargo.toml index 61173579..62e64a62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,9 @@ members = [ "p4", "x4c", "x4c_error_codes", + "codegen/common", "codegen/rust", + "codegen/htq", "lang/p4rs", "lang/p4-macro", "lang/p4-macro-test", @@ -16,7 +18,9 @@ members = [ [workspace.dependencies] p4-macro = { path = "lang/p4-macro" } +p4-cg = { path = "codegen/common" } p4-rust = { path = "codegen/rust" } +p4-htq = { path = "codegen/htq" } p4rs = { path = "lang/p4rs" } tests = { path = "test" } @@ -24,6 +28,7 @@ anyhow = "1" bitvec = "1.0" clap = { version = "4", features = ["color", "derive"] } colored = "3" +htq = { git = "https://github.com/oxidecomputer/htq", branch = "sector-001" } libloading = { version = "0.8" } num = { version = "0.4", features = ["serde"] } p4 = { path = "p4" } @@ -37,5 +42,6 @@ serde = "1.0" serde_tokenstream = "0.2" syn = "2.0" tempfile = "3.3" +thiserror = "1.0.63" usdt = "0.5.0" xfr = { git = "https://github.com/oxidecomputer/xfr" } diff --git a/codegen/common/Cargo.toml b/codegen/common/Cargo.toml new file mode 100644 index 00000000..325a2029 --- /dev/null +++ b/codegen/common/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "p4-cg" +version = "0.1.0" +edition = "2021" + +[dependencies] +p4 = { path = "../../p4" } +thiserror = "1.0.63" diff --git a/codegen/common/src/lib.rs b/codegen/common/src/lib.rs new file mode 100644 index 00000000..9647d0e8 --- /dev/null +++ b/codegen/common/src/lib.rs @@ -0,0 +1 @@ +// Copyright 2024 Oxide Computer Company diff --git a/codegen/htq/Cargo.toml b/codegen/htq/Cargo.toml new file mode 100644 index 00000000..56bbcdd7 --- /dev/null +++ b/codegen/htq/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "p4-htq" +version = "0.1.0" +edition = "2021" + +[dependencies] +p4.workspace = true +htq.workspace = true +thiserror.workspace = true diff --git a/codegen/htq/src/control.rs b/codegen/htq/src/control.rs new file mode 100644 index 00000000..7ce7fa9a --- /dev/null +++ b/codegen/htq/src/control.rs @@ -0,0 +1,270 @@ +// Copyright 2024 Oxide Computer Company + +use std::collections::HashMap; + +use htq::ast::{Parameter, Register}; +use p4::{ast::ControlParameter, hlir::Hlir}; + +use crate::{ + error::CodegenError, p4_type_to_htq_type, statement::emit_statement, + AsyncFlagAllocator, P4Context, RegisterAllocator, TableContext, +}; + +pub(crate) fn emit_control_functions( + ast: &p4::ast::AST, + hlir: &Hlir, + afa: &mut AsyncFlagAllocator, +) -> Result, CodegenError> { + let mut result = Vec::default(); + + for control in &ast.controls { + let cf = emit_control(ast, hlir, control, afa)?; + result.extend(cf.into_iter()); + } + + Ok(result) +} + +fn emit_control( + ast: &p4::ast::AST, + hlir: &Hlir, + control: &p4::ast::Control, + afa: &mut AsyncFlagAllocator, +) -> Result, CodegenError> { + let mut result = Vec::default(); + let mut psub = HashMap::>::default(); + let mut table_context = TableContext::default(); + + let mut parameters = Vec::new(); + let mut apply_return_signature = Vec::new(); + let mut action_return_signature = Vec::new(); + for x in &control.parameters { + let typ = p4_type_to_htq_type(&x.ty)?; + if x.direction.is_out() { + // TODO the special case nature of lookup results is quite + // unfortunate + if x.ty.is_lookup_result() { + //hit + apply_return_signature.push(htq::ast::Type::Bool); + //variant + apply_return_signature.push(htq::ast::Type::Unsigned(16)); + + let info = control + .resolve_lookup_result_args_size(&x.name, ast) + .ok_or(CodegenError::LookupResultArgSize(x.clone()))?; + + apply_return_signature + .push(htq::ast::Type::Unsigned(info.max_arg_size)); + + if x.ty.is_sync() { + //async flag + apply_return_signature.push(htq::ast::Type::Unsigned(128)); + } + } else { + apply_return_signature.push(typ.clone()); + action_return_signature.push(typ.clone()); + } + } + let p = htq::ast::Parameter { + reg: htq::ast::Register::new(x.name.as_str()), + typ, + }; + parameters.push((p, x.clone())); + } + + result.push(emit_control_apply( + ast, + hlir, + control, + ¶meters, + &apply_return_signature, + afa, + &mut psub, + &mut table_context, + )?); + + for action in &control.actions { + result.push(emit_control_action( + ast, + hlir, + control, + action, + ¶meters, + &action_return_signature, + afa, + &mut psub, + &mut table_context, + )?); + } + Ok(result) +} + +fn emit_control_apply( + ast: &p4::ast::AST, + hlir: &Hlir, + control: &p4::ast::Control, + parameters: &[(Parameter, p4::ast::ControlParameter)], + return_signature: &[htq::ast::Type], + afa: &mut AsyncFlagAllocator, + psub: &mut HashMap>, + table_context: &mut TableContext, +) -> Result { + let mut ra = RegisterAllocator::default(); + let mut names = control.names(); + let mut statements = Vec::default(); + let mut blocks = Vec::default(); + + for (p, p4p) in parameters { + if psub.get(p4p).is_some() { + continue; + } + ra.alloc(&p.reg.0); + } + + for s in &control.apply.statements { + let (stmts, blks) = emit_statement( + s, + ast, + &P4Context::Control(control), + hlir, + &mut names, + &mut ra, + afa, + psub, + table_context, + )?; + statements.extend(stmts); + blocks.extend(blks); + } + + let mut signature = Vec::new(); + let mut return_registers = Vec::new(); + + for (p, p4p) in parameters { + if p4p.direction.is_out() { + if let Some(substituted) = psub.get(p4p) { + return_registers.extend( + substituted + .clone() + .into_iter() + .map(|x| ra.get_reg(&x).unwrap()), + ); + } else { + signature.push(p.clone()); + return_registers.push(ra.get_reg(&p.reg).unwrap()); + } + } + } + + statements.push(htq::ast::Statement::Return(htq::ast::Return { + registers: return_registers, + })); + + statements.sort_by(|a, b| { + matches!(a, htq::ast::Statement::Label(_, _)) + .partial_cmp(&matches!(b, htq::ast::Statement::Label(_, _))) + .unwrap() + }); + + let f = htq::ast::Function { + name: format!("{}_apply", control.name), + parameters: signature, + statements, + blocks, + return_signature: return_signature.to_vec(), + }; + Ok(f) +} + +fn emit_control_action( + ast: &p4::ast::AST, + hlir: &Hlir, + control: &p4::ast::Control, + action: &p4::ast::Action, + parameters: &[(Parameter, p4::ast::ControlParameter)], + return_signature: &[htq::ast::Type], + afa: &mut AsyncFlagAllocator, + psub: &mut HashMap>, + table_context: &mut TableContext, +) -> Result { + let mut ra = RegisterAllocator::default(); + let mut names = control.names(); + let mut statements = Vec::default(); + let mut blocks = Vec::default(); + let parameters: Vec<(Parameter, p4::ast::ControlParameter)> = parameters + .iter() + .filter(|(_, p4p)| !p4p.ty.is_lookup_result()) + .cloned() + .collect(); + let mut action_parameters: Vec = + parameters.iter().cloned().map(|x| x.0).collect(); + + for x in &action.parameters { + let p = htq::ast::Parameter { + reg: htq::ast::Register::new(x.name.as_str()), + typ: p4_type_to_htq_type(&x.ty)?, + }; + action_parameters.push(p); + } + + for (p, p4p) in ¶meters { + if psub.get(p4p).is_some() { + continue; + } + ra.alloc(&p.reg.0); + } + + for s in &action.statement_block.statements { + let (stmts, blks) = emit_statement( + s, + ast, + &P4Context::Control(control), + hlir, + &mut names, + &mut ra, + afa, + psub, + table_context, + )?; + statements.extend(stmts); + blocks.extend(blks); + } + + let mut signature = Vec::new(); + let mut return_registers = Vec::new(); + + for (p, p4p) in ¶meters { + if p4p.direction.is_out() { + if let Some(substituted) = psub.get(p4p) { + for x in substituted { + if let Some(r) = ra.get_reg(x) { + return_registers.push(r.clone()) + } + } + /* + return_registers.extend( + substituted + .clone() + .into_iter() + .map(|x| ra.get_reg(&x).unwrap()), + ); + */ + } else { + signature.push(p.clone()); + return_registers.push(ra.get_reg(&p.reg).unwrap()); + } + } + } + statements.push(htq::ast::Statement::Return(htq::ast::Return { + registers: return_registers, + })); + + let f = htq::ast::Function { + name: format!("{}_{}", control.name, action.name), + parameters: action_parameters, + statements, + blocks, + return_signature: return_signature.to_vec(), + }; + Ok(f) +} diff --git a/codegen/htq/src/error.rs b/codegen/htq/src/error.rs new file mode 100644 index 00000000..1647d9e9 --- /dev/null +++ b/codegen/htq/src/error.rs @@ -0,0 +1,144 @@ +// Copyright 2024 Oxide Computer Company + +use htq::ast::Register; +use p4::{ + ast::{ + Call, Control, ControlParameter, DeclarationInfo, Expression, Lvalue, + Transition, Type, + }, + lexer::Token, +}; +use thiserror::Error; + +use crate::{RegisterAllocator, TableContext}; + +#[derive(Error, Debug)] +pub enum FlagAllocationError { + #[error("flag overflow: count exceeds 128")] + Overflow, +} + +#[derive(Error, Debug)] +pub enum CodegenError { + #[error("There is no equivalent htq type for {0}")] + NoEquivalentType(p4::ast::Type), + + #[error("undefined lvalue \n{0:#?}")] + UndefinedLvalue(Lvalue), + + #[error("cannot assign to \n{0:#?}")] + InvalidAssignment(DeclarationInfo), + + #[error("cannot convert numeric type \n{0:#?}\n to u128")] + NumericConversion(Expression), + + #[error( + "no type information for \n{0:#?}\nthis is likely a type checking bug" + )] + UntypedExpression(Expression), + + #[error( + "parent {0} for member for \n{1:#?}\nnot found: this is likely a front end bug" + )] + MemberParentNotFound(String, Lvalue), + + #[error( + "expected parent of lvalue \n{0:#?}\nto be a struct: this is likely a front end bug" + )] + ExpectedStructParent(Lvalue), + + #[error( + "expected parent of lvalue \n{0:#?}\nto be a header: this is likely a front end bug" + )] + ExpectedHeaderParent(Lvalue), + + #[error("offset for struct or header member \n{0:#?}\nnot found")] + MemberOffsetNotFound(Lvalue), + + #[error("header member {0}\n{1:#?}\nnot found")] + MemberNotFound(String, Lvalue), + + #[error("user defined type {0} not found \n{1:#?}")] + UserDefinedTypeNotFound(String, Token), + + #[error("cannot calculate offset into extern {0} \n{1:#?}")] + CannotOffsetExtern(String, Token), + + #[error("expressions appearing on the RHS of an assignment must have a value\n{0:#?}")] + AssignmentExpressionRequiresValue(Expression), + + #[error("parent for call \n{0:#?}\nnot found")] + CallParentNotFound(Call), + + #[error("cannot make a call on parent type {1}\n{0:#?}")] + InvalidCallParent(Call, Type), + + #[error("calls are not supported in parsers\n{0:#?}")] + CallInParser(Expression), + + #[error("table {0} not found in control \n{1:#?}")] + TableNotFound(String, Control), + + #[error("flag allocation: {0}")] + FlagAllocation(#[from] FlagAllocationError), + + #[error("key extraction produced no value for \n{0:#?}")] + KeyExtractionProducedNoValue(Lvalue), + + #[error("could not determine lookup result arg size for \n{0:#?}")] + LookupResultArgSize(ControlParameter), + + #[error("register does not exist for lvalue \n{0:#?}")] + RegisterDoesNotExistForLval(Lvalue), + + #[error("expected control type for \n{0:#?}\nfound \n{1:#?}")] + ExpectedControl(Lvalue, Type), + + #[error("a value is required for expression \n{0:#?}")] + ExpressionValueNeeded(Expression), + + #[error("a singular value is required for expression \n{0:#?}")] + SingularExpressionValueNeeded(Expression), + + #[error("missing register for lvalue, this is a compiler bug \n{0:#?}\ncurrent registers: \n{1:#?}")] + MissingRegisterForLvalue(Lvalue, Vec), + + #[error("table not found in context \nlvalue:\n{0:#?}\ncontext:\n{1:#?}")] + TableNotFoundInContext(Lvalue, TableContext), + + #[error("indirect action call in parser for \n{0:#?}")] + IndirectActionCallInParser(Lvalue), + + #[error("no register for parameter {0}\nregisters:\n{1:#?}")] + NoRegisterForParameter(String, RegisterAllocator), + + #[error("action not found in control\naction:\n{0:#?}\ncontrol:\n{1:#?}")] + ActionNotFound(Lvalue, Control), + + #[error("transition must be in parser context\n{0:#?}")] + TransitionOutsideParser(Transition), + + #[error("call does not have enough arguments\n{0:#?}")] + NotEnoughArgs(Lvalue), + + #[error("expected expression got\n{0:#?}")] + ExpectedLvalue(Expression), + + #[error("header declaration not found\n{0:#?}")] + HeaderDeclNotFound(Lvalue), + + #[error("expected header type for lvalue\n{0:#?}")] + ExpectedHeaderType(Lvalue), + + #[error("header definition for type {0} not found for lvalue\n{1:#?}")] + HeaderDefnNotFound(String, Lvalue), +} + +#[derive(Error, Debug)] +pub enum EmitError { + #[error("io error: {0}")] + Io(#[from] std::io::Error), + + #[error("codegen error: {0}")] + Codegen(#[from] CodegenError), +} diff --git a/codegen/htq/src/expression.rs b/codegen/htq/src/expression.rs new file mode 100644 index 00000000..c286ed39 --- /dev/null +++ b/codegen/htq/src/expression.rs @@ -0,0 +1,963 @@ +use std::collections::HashMap; + +use htq::ast::{ + Beq, Fget, Load, Lookup, Or, Register, Rset, Shl, Statement, Type, Value, +}; +use p4::{ + ast::{ + type_size, BinOp, DeclarationInfo, Expression, ExpressionKind, Lvalue, + NameInfo, + }, + hlir::Hlir, +}; + +// Copyright 2024 Oxide Computer Company +use crate::{ + error::CodegenError, p4_type_to_htq_type, statement::member_offsets, + AsyncFlagAllocator, CompiledTableInfo, P4Context, RegisterAllocator, + TableContext, VersionedRegister, +}; + +pub(crate) struct ExpressionValue { + // register the value of the expression is held in along with their types + pub(crate) registers: Vec<(Register, Type)>, + // sync flag associated with the expression + #[allow(dead_code)] + pub(crate) sync_flag: Option, +} + +impl ExpressionValue { + fn new(register: Register, typ: Type) -> Self { + Self { + registers: vec![(register, typ)], + sync_flag: None, + } + } + + #[allow(dead_code)] + fn new_async(register: Register, typ: Type, sync_flag: u128) -> Self { + Self { + registers: vec![(register, typ)], + sync_flag: Some(sync_flag), + } + } +} + +// Builds a vector of statements that implement the expression. Returns the +// statements and the resulting value of the expression, if any. +pub(crate) fn emit_expression( + expr: &Expression, + hlir: &Hlir, + ast: &p4::ast::AST, + context: &P4Context<'_>, + ra: &mut RegisterAllocator, + afa: &mut AsyncFlagAllocator, + names: &HashMap, + table_context: &mut TableContext, +) -> Result< + ( + Vec, + Vec, + Option, + ), + CodegenError, +> { + let r = ra.alloc_tmp_for_token(&expr.token); + match &expr.kind { + ExpressionKind::BoolLit(value) => { + let (stmts, value) = emit_bool_lit(*value, r)?; + Ok((stmts, Vec::default(), value)) + } + ExpressionKind::BitLit(width, value) => { + let (stmts, value) = emit_bit_lit(*width, *value, r, expr)?; + Ok((stmts, Vec::default(), value)) + } + ExpressionKind::IntegerLit(value) => { + let (stmts, value) = emit_int_lit(*value, r)?; + Ok((stmts, Vec::default(), value)) + } + ExpressionKind::SignedLit(width, value) => { + let (stmts, value) = emit_signed_lit(*width, *value, r)?; + Ok((stmts, Vec::default(), value)) + } + ExpressionKind::Lvalue(lval) => { + let (stmts, value) = emit_lval(lval, hlir, ast, ra, names)?; + Ok((stmts, Vec::default(), value)) + } + ExpressionKind::Call(call) => match context { + P4Context::Control(c) => { + let (stmts, blks, value) = emit_call_in_control( + call, + c, + hlir, + ast, + context, + ra, + afa, + names, + table_context, + )?; + Ok((stmts, blks, value)) + } + P4Context::Parser(_) => { + Err(CodegenError::CallInParser(expr.clone())) + } + }, + ExpressionKind::Binary(lhs, op, rhs) => { + let (stmts, value) = emit_binary_expr( + lhs.as_ref(), + op, + rhs.as_ref(), + hlir, + ast, + ra, + afa, + names, + )?; + Ok((stmts, Vec::default(), value)) + } + xpr => todo!("expression: {xpr:?}"), + } +} + +pub(crate) fn emit_single_valued_expression( + expr: &Expression, + hlir: &Hlir, + ast: &p4::ast::AST, + context: &P4Context<'_>, + ra: &mut RegisterAllocator, + afa: &mut AsyncFlagAllocator, + names: &HashMap, + table_context: &mut TableContext, +) -> Result< + (Vec, Vec, Register), + CodegenError, +> { + let (stmts, blocks, val) = emit_expression( + expr, + hlir, + ast, + context, + ra, + afa, + names, + table_context, + )?; + + let val = val.ok_or(CodegenError::ExpressionValueNeeded(expr.clone()))?; + if val.registers.len() != 1 { + return Err(CodegenError::SingularExpressionValueNeeded(expr.clone())); + } + + Ok((stmts, blocks, val.registers[0].0.clone())) +} + +pub(crate) fn emit_binary_expr( + lhs: &Expression, + op: &BinOp, + rhs: &Expression, + hlir: &Hlir, + ast: &p4::ast::AST, + ra: &mut RegisterAllocator, + afa: &mut AsyncFlagAllocator, + names: &HashMap, +) -> Result<(Vec, Option), CodegenError> { + match op { + BinOp::Add => todo!("bin op add"), + BinOp::Subtract => todo!("bin op subtract"), + BinOp::Mod => todo!("bin op mod"), + BinOp::Geq => todo!("bin op geq"), + BinOp::Gt => todo!("bin op gt"), + BinOp::Leq => todo!("bin op leq"), + BinOp::Lt => todo!("bin op lt"), + BinOp::Eq => emit_binary_expr_eq(lhs, rhs, hlir, ast, ra, afa, names), + BinOp::Mask => todo!("bin op mask"), + BinOp::NotEq => todo!("bin op not eq"), + BinOp::BitAnd => todo!("bin op bit and"), + BinOp::BitOr => todo!("bin op bit or"), + BinOp::Xor => todo!("bin op xor"), + } +} + +pub(crate) fn emit_binary_expr_eq( + _lhs: &Expression, + _rhs: &Expression, + _hlir: &Hlir, + _ast: &p4::ast::AST, + _ra: &mut RegisterAllocator, + _afa: &mut AsyncFlagAllocator, + _names: &HashMap, +) -> Result<(Vec, Option), CodegenError> { + todo!() +} + +pub(crate) fn emit_call_in_parser( + call: &p4::ast::Call, + parser: &p4::ast::Parser, + hlir: &Hlir, + ast: &p4::ast::AST, + ra: &mut RegisterAllocator, + afa: &mut AsyncFlagAllocator, + names: &HashMap, + table_context: &mut TableContext, +) -> Result< + ( + Vec, + Vec, + Option, + ), + CodegenError, +> { + match call.lval.leaf() { + "extract" => { + let (stmts, result) = emit_extract_call( + call, + parser, + hlir, + ast, + ra, + afa, + names, + table_context, + )?; + Ok((stmts, Vec::default(), result)) + } + x => { + todo!("unhandled parser function: {x:#?}"); + } + } +} + +pub(crate) fn emit_call_in_control( + call: &p4::ast::Call, + control: &p4::ast::Control, + hlir: &Hlir, + ast: &p4::ast::AST, + context: &P4Context<'_>, + ra: &mut RegisterAllocator, + afa: &mut AsyncFlagAllocator, + names: &HashMap, + table_context: &mut TableContext, +) -> Result< + ( + Vec, + Vec, + Option, + ), + CodegenError, +> { + match call.lval.leaf() { + "apply" => { + let (stmts, result) = emit_apply_call( + call, + control, + hlir, + ast, + ra, + afa, + names, + table_context, + )?; + Ok((stmts, Vec::default(), result)) + } + "act" => emit_indirect_action_call( + call, + hlir, + ast, + context, + ra, + names, + table_context, + ), + "setValid" => { + let (stmts, result) = + emit_set_valid_call(call, hlir, ast, ra, names, true)?; + Ok((stmts, Vec::default(), result)) + } + "setInvalid" => { + let (stmts, result) = + emit_set_valid_call(call, hlir, ast, ra, names, false)?; + Ok((stmts, Vec::default(), result)) + } + "isValid" => { + let (stmts, result) = + emit_is_valid_call(call, hlir, ast, ra, names)?; + Ok((stmts, Vec::default(), result)) + } + _ => { + let (stmts, result) = emit_extern_call(call, hlir, ast, ra, names)?; + Ok((stmts, Vec::default(), result)) + } + } +} + +fn emit_extract_call( + call: &p4::ast::Call, + parser: &p4::ast::Parser, + _hlir: &Hlir, + ast: &p4::ast::AST, + ra: &mut RegisterAllocator, + _afa: &mut AsyncFlagAllocator, + names: &HashMap, + _table_context: &mut TableContext, +) -> Result<(Vec, Option), CodegenError> { + let src = &parser.parameters[0].name; + let source = ra.get(src).ok_or(CodegenError::NoRegisterForParameter( + src.to_owned(), + ra.clone(), + ))?; + + let tgt = call + .args + .first() + .ok_or(CodegenError::NotEnoughArgs(call.lval.clone()))?; + let tgt = match &tgt.kind { + ExpressionKind::Lvalue(lval) => lval, + _ => return Err(CodegenError::ExpectedLvalue(tgt.as_ref().clone())), + }; + let target = ra + .get(tgt.root()) + .ok_or(CodegenError::RegisterDoesNotExistForLval(tgt.clone()))?; + let output = ra.alloc(tgt.root()); + + let info = names + .get(tgt.root()) + .ok_or(CodegenError::HeaderDeclNotFound(tgt.clone()))?; + + let typename = match &info.ty { + p4::ast::Type::UserDefined(name, _) => name.clone(), + _ => return Err(CodegenError::ExpectedHeaderType(tgt.clone())), + }; + + let offset = if let Some(hdr) = ast.get_header(&typename) { + hdr.index_of(tgt.leaf()) + .ok_or(CodegenError::MemberOffsetNotFound(tgt.clone()))? + } else { + let st = ast.get_struct(&typename).ok_or( + CodegenError::HeaderDefnNotFound(typename.clone(), tgt.clone()), + )?; + st.index_of(tgt.leaf()) + .ok_or(CodegenError::MemberOffsetNotFound(tgt.clone()))? + }; + + let sz = type_size(&info.ty, ast); + + let offset_reg = + ra.get("offset") + .ok_or(CodegenError::NoRegisterForParameter( + String::from("offset"), + ra.clone(), + ))?; + + let extract_stmt = htq::ast::Extract { + output, + target, + target_offset: Value::number(offset as i128), + source, + source_offset: Value::reg(offset_reg.clone()), //TODO + }; + + let add_offset_stmt = htq::ast::Add { + target: ra.alloc(&offset_reg.0), + typ: Type::Unsigned(32), + source_a: Value::reg(offset_reg), + source_b: Value::number(sz as i128), + }; + + Ok(( + vec![ + Statement::Extract(extract_stmt), + Statement::Add(add_offset_stmt), + ], + None, + )) +} + +fn emit_apply_call( + call: &p4::ast::Call, + control: &p4::ast::Control, + hlir: &Hlir, + ast: &p4::ast::AST, + ra: &mut RegisterAllocator, + afa: &mut AsyncFlagAllocator, + names: &HashMap, + table_context: &mut TableContext, +) -> Result<(Vec, Option), CodegenError> { + let parent = call.lval.pop_right(); + let parent = parent.leaf(); + let info = names + .get(parent) + .ok_or(CodegenError::CallParentNotFound(call.clone()))?; + + match &info.ty { + p4::ast::Type::Table => { + emit_table_apply_call(call, control, hlir, ast, ra, afa, names) + } + p4::ast::Type::UserDefined(name, _) => { + // validate user defined type is a control + ast.get_control(name) + .ok_or(CodegenError::InvalidCallParent( + call.clone(), + info.ty.clone(), + ))?; + emit_control_apply_call(call, hlir, ast, ra, names, table_context) + } + p4::ast::Type::Action => { + emit_direct_action_call(call, hlir, ast, ra, names, table_context) + } + p4::ast::Type::ExternFunction => { + emit_extern_call(call, hlir, ast, ra, names) + } + typ => Err(CodegenError::InvalidCallParent(call.clone(), typ.clone())), + } +} + +fn emit_table_apply_call( + call: &p4::ast::Call, + control: &p4::ast::Control, + hlir: &Hlir, + ast: &p4::ast::AST, + ra: &mut RegisterAllocator, + afa: &mut AsyncFlagAllocator, + names: &HashMap, +) -> Result<(Vec, Option), CodegenError> { + // %hit, %variant, %args = async 2 lookup proxy_arp %key; + let table = call.lval.pop_right(); + let table = table.leaf(); + let table = control.get_table(table).ok_or(CodegenError::TableNotFound( + table.to_owned(), + control.clone(), + ))?; + + let sync_flag = afa.allocate()?; + + let hit = VersionedRegister::for_token("hit", &call.lval.token); + let variant = VersionedRegister::for_token("variant", &call.lval.token); + let args = VersionedRegister::for_token("args", &call.lval.token); + let sync_flag_reg = + VersionedRegister::for_token("sync_flag", &call.lval.token); + let mut key = VersionedRegister::for_token("key", &call.lval.token); + let mut instrs = Vec::new(); + + let mut total_key_size = 0; + for (k, _) in &table.key { + let info = hlir + .lvalue_decls + .get(k) + .ok_or(CodegenError::UndefinedLvalue(k.clone()))?; + total_key_size += type_size(&info.ty, ast); + } + + let key_typ = Type::Bitfield(total_key_size); + + instrs.push(Statement::Rset(Rset { + target: key.to_reg(), + typ: key_typ.clone(), + source: Value::number(0), + })); + + instrs.push(Statement::Rset(Rset { + target: sync_flag_reg.to_reg(), + typ: Type::Bitfield(128), + source: Value::number(sync_flag as i128), + })); + + let mut offset = 0u128; + for (k, _) in &table.key { + let (key_extract_statements, extracted_value) = + emit_lval(k, hlir, ast, ra, names)?; + let extracted_value = extracted_value + .ok_or(CodegenError::KeyExtractionProducedNoValue(k.clone()))?; + instrs.extend(key_extract_statements.into_iter()); + + let tmp = VersionedRegister::for_token("key", &k.token); + instrs.push(Statement::Shl(Shl { + target: tmp.to_reg(), + typ: key_typ.clone(), + source: Value::register(&extracted_value.registers[0].0 .0), + amount: Value::number(offset as i128), + })); + + let curr_key = key.clone(); + instrs.push(Statement::Or(Or { + target: key.next().to_reg(), + typ: key_typ.clone(), + source_a: Value::reg(curr_key.to_reg()), + source_b: Value::reg(tmp.to_reg()), + })); + offset += extracted_value.registers[0].1.bit_size().unwrap() as u128; + } + + let lookup_instr = Statement::Lookup(Lookup { + hit: hit.to_reg(), + variant: variant.to_reg(), + args: args.to_reg(), + asynchronous: if table.is_async { + Some(htq::ast::Async { + identifier: Value::number(sync_flag as i128), + }) + } else { + None + }, + table: table.name.clone(), + key: Value::register(&key.name()), + }); + instrs.push(lookup_instr); + + let args_size = control.maximum_action_arg_length_for_table(ast, table); + + Ok(( + instrs, + Some(ExpressionValue { + registers: vec![ + (hit.to_reg(), Type::Bool), + (variant.to_reg(), Type::Unsigned(16)), + (args.to_reg(), Type::Unsigned(args_size)), + (sync_flag_reg.to_reg(), Type::Bitfield(128)), + ], + sync_flag: Some(sync_flag), + }), + )) +} + +fn emit_control_apply_call( + call: &p4::ast::Call, + _hlir: &Hlir, + ast: &p4::ast::AST, + ra: &mut RegisterAllocator, + names: &HashMap, + table_context: &mut TableContext, +) -> Result<(Vec, Option), CodegenError> { + // get the control being called + let info = names + .get(call.lval.root()) + .ok_or(CodegenError::CallParentNotFound(call.clone()))?; + + let control_type_name = match &info.ty { + p4::ast::Type::UserDefined(name, _) => name.to_owned(), + _x => { + return Err(CodegenError::ExpectedControl( + call.lval.clone(), + info.ty.clone(), + )) + } + }; + + let control = ast + .get_control(&control_type_name) + .ok_or(CodegenError::CallParentNotFound(call.clone()))?; + + // determine argument registers + let mut arg_values = Vec::default(); + for a in &call.args { + match &a.kind { + ExpressionKind::Lvalue(lval) => { + let info = names + .get(lval.root()) + .ok_or(CodegenError::UndefinedLvalue(lval.clone()))?; + if info.ty.is_lookup_result() { + continue; + } + let reg = ra.get(&lval.name).ok_or( + CodegenError::RegisterDoesNotExistForLval(lval.clone()), + )?; + arg_values.push(Value::Register(reg)); + } + ExpressionKind::BitLit(_width, value) => { + arg_values.push(Value::number(*value as i128)); + } + ExpressionKind::SignedLit(_width, value) => { + arg_values.push(Value::number(*value)); + } + ExpressionKind::IntegerLit(value) => { + arg_values.push(Value::number(*value)); + } + ExpressionKind::BoolLit(value) => { + arg_values.push(Value::number(*value as i128)); + } + _ => todo!("call argument type {:#?}", a.kind), + }; + } + + // determine return registers + let mut returned_registers = Vec::default(); + for (i, p) in control.parameters.iter().enumerate() { + if p.direction.is_out() { + if p.ty.is_lookup_result() { + let arg_lval = match &call.args[i].kind { + ExpressionKind::Lvalue(lval) => lval, + _x => panic!("expected lvalue for out parameter"), + }; + let arg = arg_lval.root().to_owned(); + let hit = ra.alloc(&format!("{}_hit", arg)); + returned_registers.push((hit.clone(), htq::ast::Type::Bool)); + + let variant = ra.alloc(&format!("{}_variant", arg)); + returned_registers + .push((variant.clone(), htq::ast::Type::Unsigned(16))); + + let args = ra.alloc(&format!("{}_args", arg)); + let info = control + .resolve_lookup_result_args_size(&p.name, ast) + .ok_or(CodegenError::LookupResultArgSize(p.clone()))?; + + returned_registers.push(( + args.clone(), + htq::ast::Type::Bitfield(info.max_arg_size), + )); + + let sync = ra.alloc(&format!("{}_sync", arg)); + returned_registers + .push((sync.clone(), htq::ast::Type::Bitfield(128))); + table_context.insert( + arg_lval.name.clone(), + CompiledTableInfo { + table: info.table.clone(), + control: control.clone(), + hit, + variant, + args, + sync, + }, + ); + } else { + returned_registers + .push((ra.alloc(&p.name), p4_type_to_htq_type(&p.ty)?)) + } + } + } + + let call_stmt = Statement::Call(htq::ast::Call { + fname: format!("{}_{}", call.lval.root(), call.lval.leaf()), + args: arg_values, + targets: returned_registers.iter().map(|x| x.0.clone()).collect(), + }); + + Ok(( + vec![call_stmt], + Some(ExpressionValue { + registers: returned_registers, + sync_flag: None, + }), + )) +} + +fn emit_set_valid_call( + _call: &p4::ast::Call, + _hlir: &Hlir, + _ast: &p4::ast::AST, + _ra: &mut RegisterAllocator, + _names: &HashMap, + _valid: bool, +) -> Result<(Vec, Option), CodegenError> { + //TODO + Ok((Vec::default(), None)) + //todo!("set valid call") +} + +fn emit_is_valid_call( + _call: &p4::ast::Call, + _hlir: &Hlir, + _ast: &p4::ast::AST, + _ra: &mut RegisterAllocator, + _names: &HashMap, +) -> Result<(Vec, Option), CodegenError> { + //TODO + Ok((Vec::default(), None)) +} + +fn emit_extern_call( + _call: &p4::ast::Call, + _hlir: &Hlir, + _ast: &p4::ast::AST, + _ra: &mut RegisterAllocator, + _names: &HashMap, +) -> Result<(Vec, Option), CodegenError> { + //TODO + Ok((Vec::default(), None)) +} + +fn emit_indirect_action_call( + call: &p4::ast::Call, + _hlir: &Hlir, + ast: &p4::ast::AST, + context: &P4Context<'_>, + ra: &mut RegisterAllocator, + _names: &HashMap, + table_context: &mut TableContext, +) -> Result< + ( + Vec, + Vec, + Option, + ), + CodegenError, +> { + let mut result = Vec::new(); + let mut blocks = Vec::new(); + + let control = match context { + P4Context::Control(c) => *c, + P4Context::Parser(_) => { + return Err(CodegenError::IndirectActionCallInParser( + call.lval.clone(), + )) + } + }; + + let table_lval = call.lval.pop_right(); + let info = table_context.get(&table_lval.name).ok_or( + CodegenError::TableNotFoundInContext( + table_lval.clone(), + table_context.clone(), + ), + )?; + + let mut block_ra = RegisterAllocator::default(); + let mut control_params = Vec::new(); + let mut block_params = Vec::new(); + for p in &control.parameters { + control_params.push(ra.get(&p.name).ok_or( + CodegenError::NoRegisterForParameter(p.name.clone(), ra.clone()), + )?); + block_params.push(block_ra.alloc(&p.name)); + } + block_params.push(info.args.clone()); + + let control_param_values = control_params + .iter() + .cloned() + .map(Value::reg) + .collect::>(); + + let block_param_values = block_params + .iter() + .cloned() + .map(Value::reg) + .collect::>(); + + let mut block_args = control_param_values.clone(); + block_args.push(Value::reg(info.args.clone())); + + for (i, a) in info.table.actions.iter().enumerate() { + // make a clean lbock for each action + let mut block_ra = block_ra.clone(); + let mut block_param_values = block_param_values.clone(); + // pop the args off, we're going to break it up into components + block_param_values.pop(); + + let action_decl = info.control.get_action(&a.name).ok_or( + CodegenError::ActionNotFound(a.clone(), info.control.clone()), + )?; + let mut control_out_params = Vec::default(); + let mut out_params = Vec::default(); + let mut stmts = Vec::default(); + + let mut action_params = Vec::default(); + + let mut offset = 0; + let key = info.args.clone(); + for x in &action_decl.parameters { + if x.direction.is_out() { + out_params.push(block_ra.alloc(&x.name)); + } + + let action_param_reg = block_ra.alloc(&format!("{}_arg", &x.name)); + let sz = type_size(&x.ty, ast); + action_params.push(action_param_reg.clone()); + stmts.push(Statement::Load(htq::ast::Load { + target: action_param_reg.clone(), + typ: Type::Bitfield(sz), + source: key.clone(), + offset: Value::number(offset), + })); + + block_param_values.push(Value::reg(action_param_reg.clone())); + + offset += sz as i128; + } + for x in &info.control.parameters { + if x.ty.is_lookup_result() { + continue; + } + if x.direction.is_out() { + out_params.push(block_ra.alloc(&x.name)); + control_out_params.push(block_ra.get(&x.name).unwrap()); + } + } + + result.push(Statement::Beq(Beq { + source: info.variant.clone(), + predicate: Value::number(i as i128), + label: a.name.clone(), + args: block_args.clone(), + })); + + stmts.push(Statement::Call(htq::ast::Call { + fname: format!("{}_{}", info.control.name, a.name.clone()), + args: block_param_values.clone(), + targets: out_params, + })); + stmts.push(Statement::Return(htq::ast::Return { + registers: control_out_params, + })); + blocks.push(htq::ast::StatementBlock { + name: a.name.clone(), + parameters: block_params.clone(), + statements: stmts, + }); + } + Ok((result, blocks, None)) +} + +fn emit_direct_action_call( + _call: &p4::ast::Call, + _hlir: &Hlir, + _ast: &p4::ast::AST, + _ra: &mut RegisterAllocator, + _names: &HashMap, + _table_context: &mut TableContext, +) -> Result<(Vec, Option), CodegenError> { + //TODO + Ok((Vec::default(), None)) +} + +fn emit_lval( + lval: &Lvalue, + hlir: &Hlir, + ast: &p4::ast::AST, + ra: &mut RegisterAllocator, + names: &HashMap, +) -> Result<(Vec, Option), CodegenError> { + let mut result: Vec = Vec::default(); + + let info = hlir + .lvalue_decls + .get(lval) + .ok_or(CodegenError::UndefinedLvalue(lval.clone()))?; + + let typ = p4_type_to_htq_type(&info.ty)?; + + match &info.decl { + DeclarationInfo::Parameter(_) => { + //TODO unify VersionedRegister and RegisterAllocator + let treg = VersionedRegister::tmp_for_token(&lval.token); + result.push(Statement::Load(Load { + target: treg.to_reg(), + typ: typ.clone(), + source: Register::new(lval.root()), + offset: Value::number(0), + })); + Ok((result, Some(ExpressionValue::new(treg.to_reg(), typ)))) + } + DeclarationInfo::ActionParameter(_) => { + let treg = VersionedRegister::tmp_for_token(&lval.token); + result.push(Statement::Load(Load { + target: treg.to_reg(), + typ: typ.clone(), + source: Register::new(lval.root()), + offset: Value::number(0), + })); + Ok((result, Some(ExpressionValue::new(treg.to_reg(), typ)))) + } + DeclarationInfo::StructMember | DeclarationInfo::HeaderMember => { + let offsets = member_offsets(ast, names, lval)?; + let treg = VersionedRegister::tmp_for_token(&lval.token); + + // TODO this is terrible, we should have one way to lookup the + // register + let name = lval.root(); + let source = if let Ok(source) = + ra.get(name).ok_or(CodegenError::MissingRegisterForLvalue( + lval.clone(), + ra.all_registers(), + )) { + source + } else { + let name = reg_name_for_lval(lval); + ra.get(&name).ok_or(CodegenError::MissingRegisterForLvalue( + lval.clone(), + ra.all_registers(), + ))? + }; + + result.push(Statement::Fget(Fget { + target: treg.to_reg(), + typ: typ.clone(), + source, + offsets, + })); + Ok((result, Some(ExpressionValue::new(treg.to_reg(), typ)))) + } + DeclarationInfo::Local => { + let name = reg_name_for_lval(lval); + let reg = + ra.get(&name).ok_or(CodegenError::MissingRegisterForLvalue( + lval.clone(), + ra.all_registers(), + ))?; + Ok((result, Some(ExpressionValue::new(reg, typ)))) + } + other => todo!("emit lval for \n{other:#?}"), + } +} + +fn reg_name_for_lval(lval: &Lvalue) -> String { + lval.name.replace('.', "_") +} + +pub(crate) fn emit_bool_lit( + value: bool, + ra: Register, +) -> Result<(Vec, Option), CodegenError> { + let instrs = vec![Statement::Rset(Rset { + target: ra.clone(), + typ: Type::Bool, + source: Value::bool(value), + })]; + Ok((instrs, Some(ExpressionValue::new(ra, Type::Bool)))) +} + +pub(crate) fn emit_bit_lit( + width: u16, + value: u128, + ra: Register, + expr: &Expression, +) -> Result<(Vec, Option), CodegenError> { + let value = i128::try_from(value) + .map_err(|_| CodegenError::NumericConversion(expr.clone()))?; + let typ = Type::Bitfield(usize::from(width)); + let instrs = vec![Statement::Rset(Rset { + typ: typ.clone(), + target: ra.clone(), + source: Value::number(value), + })]; + Ok((instrs, Some(ExpressionValue::new(ra, typ)))) +} + +pub(crate) fn emit_int_lit( + value: i128, + ra: Register, +) -> Result<(Vec, Option), CodegenError> { + let typ = Type::Signed(128); + let instrs = vec![Statement::Rset(Rset { + typ: typ.clone(), + target: ra.clone(), + source: Value::number(value), + })]; + Ok((instrs, Some(ExpressionValue::new(ra, typ)))) +} + +pub(crate) fn emit_signed_lit( + width: u16, + value: i128, + ra: Register, +) -> Result<(Vec, Option), CodegenError> { + let typ = Type::Signed(usize::from(width)); + let instrs = vec![Statement::Rset(Rset { + typ: typ.clone(), + target: ra.clone(), + source: Value::number(value), + })]; + Ok((instrs, Some(ExpressionValue::new(ra, typ)))) +} diff --git a/codegen/htq/src/header.rs b/codegen/htq/src/header.rs new file mode 100644 index 00000000..1c5eef66 --- /dev/null +++ b/codegen/htq/src/header.rs @@ -0,0 +1,41 @@ +// Copyright 2024 Oxide Computer Company + +use crate::{error::CodegenError, p4_type_to_htq_type}; + +pub(crate) fn p4_header_to_htq_header( + p4h: &p4::ast::Header, +) -> Result { + Ok(htq::ast::Header { + name: p4h.name.clone(), + fields: p4h + .members + .iter() + .map(p4_header_member_to_htq_type) + .collect::, CodegenError>>()?, + }) +} + +pub(crate) fn p4_struct_to_htq_header( + p4s: &p4::ast::Struct, +) -> Result { + Ok(htq::ast::Header { + name: p4s.name.clone(), + fields: p4s + .members + .iter() + .map(p4_struct_member_to_htq_type) + .collect::, CodegenError>>()?, + }) +} + +pub(crate) fn p4_header_member_to_htq_type( + p4f: &p4::ast::HeaderMember, +) -> Result { + p4_type_to_htq_type(&p4f.ty) +} + +pub(crate) fn p4_struct_member_to_htq_type( + p4f: &p4::ast::StructMember, +) -> Result { + p4_type_to_htq_type(&p4f.ty) +} diff --git a/codegen/htq/src/lib.rs b/codegen/htq/src/lib.rs new file mode 100644 index 00000000..ecf9a9ea --- /dev/null +++ b/codegen/htq/src/lib.rs @@ -0,0 +1,301 @@ +// Copyright 2024 Oxide Computer Company + +#![allow(clippy::too_many_arguments)] +#![allow(clippy::result_large_err)] +#![allow(clippy::type_complexity)] + +use std::{ + collections::{BTreeMap, HashMap}, + io::Write, +}; + +use control::emit_control_functions; +use error::{CodegenError, FlagAllocationError}; +use header::{p4_header_to_htq_header, p4_struct_to_htq_header}; +use htq::{ast::Register, emit::Emit}; +use p4::{hlir::Hlir, lexer::Token}; +use parser::emit_parser_functions; +use table::p4_table_to_htq_table; + +use crate::error::EmitError; + +mod control; +mod error; +mod expression; +mod header; +mod parser; +mod statement; +mod table; + +pub fn emit( + ast: &p4::ast::AST, + hlir: &Hlir, + filename: &str, +) -> Result<(), EmitError> { + let mut afa = AsyncFlagAllocator::default(); + let mut headers: Vec<_> = + ast.headers + .iter() + .map(p4_header_to_htq_header) + .collect::, CodegenError>>()?; + + headers.extend( + ast.structs + .iter() + .map(p4_struct_to_htq_header) + .collect::, CodegenError>>()?, + ); + + let tables: Vec<_> = ast + .controls + .iter() + .flat_map(|c| c.tables.iter().map(|t| (c, t)).collect::>()) + .map(|(c, t)| p4_table_to_htq_table(c, t, hlir)) + .collect::, CodegenError>>()?; + + let parser_functions: Vec<_> = emit_parser_functions(ast, hlir, &mut afa)?; + let control_functions: Vec<_> = + emit_control_functions(ast, hlir, &mut afa)?; + + // code generation done, now write out the htq AST to a file + + let mut f = std::fs::File::create(filename)?; + + writeln!( + f, + "parser_entry {}_start;", + ast.package_instance + .as_ref() + .unwrap() + .parameters + .first() + .expect("package instance has parser entry point") + )?; + writeln!( + f, + "entry {}_apply;", + ast.package_instance + .as_ref() + .unwrap() + .parameters + .get(1) + .expect("package instance has entry point") + )?; + + for h in &headers { + writeln!(f, "{}", h.emit())?; + } + writeln!(f)?; + + for t in &tables { + writeln!(f, "{}", t.emit())?; + } + writeln!(f)?; + + for func in &parser_functions { + writeln!(f, "{}", func.emit())?; + } + writeln!(f)?; + + for func in &control_functions { + writeln!(f, "{}", func.emit())?; + } + writeln!(f)?; + + Ok(()) +} + +fn p4_type_to_htq_type( + p4ty: &p4::ast::Type, +) -> Result { + Ok(match p4ty { + p4::ast::Type::Bool => htq::ast::Type::Bool, + p4::ast::Type::Bit(n) => htq::ast::Type::Bitfield(*n), + p4::ast::Type::Varbit(n) => htq::ast::Type::Bitfield(*n), + p4::ast::Type::Int(n) => htq::ast::Type::Unsigned(*n), + p4::ast::Type::UserDefined(name, _) => { + htq::ast::Type::User(name.clone()) + } + p4::ast::Type::Sync(_) => { + htq::ast::Type::User(String::from("lookup_result")) + //htq::ast::Type::Signed(128) + //return Err(CodegenError::NoEquivalentType(t.clone())) + } + t @ p4::ast::Type::Table => { + return Err(CodegenError::NoEquivalentType(t.clone())) + } + t @ p4::ast::Type::Error => { + return Err(CodegenError::NoEquivalentType(t.clone())) + } + t @ p4::ast::Type::Void => { + return Err(CodegenError::NoEquivalentType(t.clone())) + } + t @ p4::ast::Type::State => { + return Err(CodegenError::NoEquivalentType(t.clone())) + } + t @ p4::ast::Type::Action => { + return Err(CodegenError::NoEquivalentType(t.clone())) + } + t @ p4::ast::Type::ExternFunction => { + return Err(CodegenError::NoEquivalentType(t.clone())) + } + t @ p4::ast::Type::HeaderMethod => { + return Err(CodegenError::NoEquivalentType(t.clone())) + } + t @ p4::ast::Type::List(_) => { + return Err(CodegenError::NoEquivalentType(t.clone())) + } + p4::ast::Type::String => todo!("string types not yet supported"), + }) +} + +#[derive(Default, Debug, Clone)] +pub struct RegisterAllocator { + data: BTreeMap, +} + +impl RegisterAllocator { + pub(crate) fn alloc(&mut self, name: &str) -> Register { + match self.data.get_mut(name) { + Some(rev) => { + *rev += 1; + Register::new(&format!("{}.{}", name, *rev)) + } + None => { + self.data.insert(name.to_owned(), 0); + Register::new(name) + } + } + } + + pub(crate) fn alloc_next(&mut self, reg: &Register) -> Register { + self.alloc(®.0) + } + + pub(crate) fn alloc_tmp_for_token( + &mut self, + tk: &Token, + ) -> htq::ast::Register { + let name = format!("tmp{}_{}", tk.line, tk.col); + self.alloc(&name) + } + + pub(crate) fn get(&self, name: &str) -> Option { + self.data + .get(name) + .map(|rev| Self::register_name(name, *rev)) + } + + pub(crate) fn get_reg(&self, reg: &Register) -> Option { + self.get(®.0) + } + + pub(crate) fn all_registers(&self) -> Vec { + self.data + .iter() + .map(|(name, rev)| Self::register_name(name, *rev)) + .collect() + } + + pub(crate) fn rebase(&self) -> Self { + Self { + data: self.data.keys().map(|k| (k.clone(), 0)).collect(), + } + } + + fn register_name(name: &str, rev: usize) -> Register { + if rev > 0 { + Register::new(&format!("{}.{}", name, rev)) + } else { + Register::new(name) + } + } +} + +/// The async flag allocator allocates bitmap entries for asynchronous +/// operations. HTQ supports up to 128 flags through an underlying u128 type. +#[derive(Default)] +pub(crate) struct AsyncFlagAllocator { + flag: u128, +} + +impl AsyncFlagAllocator { + pub(crate) fn allocate(&mut self) -> Result { + if self.flag == u128::MAX { + return Err(FlagAllocationError::Overflow); + } + // simple in-order allocation with no deallocation for now, + // can make this more sophisticated with deallocation and + // a back-filling allocator later ... + let pos = self.flag.leading_ones(); + let value = 1 << pos; + self.flag |= value; + Ok(value) + } +} + +#[derive(Debug, Clone)] +pub(crate) struct VersionedRegister { + pub(crate) reg: Register, + pub(crate) version: usize, +} + +impl VersionedRegister { + pub(crate) fn tmp_for_token(tk: &Token) -> Self { + Self { + reg: Register::new(&format!("tmp{}_{}", tk.line, tk.col)), + version: 0, + } + } + pub(crate) fn for_token(prefix: &str, tk: &Token) -> Self { + Self { + reg: Register::new(&format!("{}{}_{}", prefix, tk.line, tk.col)), + version: 0, + } + } + #[allow(dead_code)] + pub(crate) fn for_name(name: &str) -> Self { + Self { + reg: Register::new(name), + version: 0, + } + } + + #[allow(dead_code)] + pub(crate) fn next(&mut self) -> &Self { + self.version += 1; + self + } + + pub(crate) fn name(&self) -> String { + if self.version == 0 { + self.reg.0.clone() + } else { + format!("{}.{}", self.reg.0, self.version) + } + } + + pub(crate) fn to_reg(&self) -> Register { + Register::new(&self.name()) + } +} + +// Codegen context +// TODO more comprehensive .... +pub(crate) enum P4Context<'a> { + #[allow(dead_code)] + Parser(&'a p4::ast::Parser), + Control(&'a p4::ast::Control), +} + +pub type TableContext = HashMap; + +#[derive(Debug, Clone)] +pub struct CompiledTableInfo { + pub table: p4::ast::Table, + pub control: p4::ast::Control, + pub hit: Register, + pub variant: Register, + pub args: Register, + pub sync: Register, +} diff --git a/codegen/htq/src/parser.rs b/codegen/htq/src/parser.rs new file mode 100644 index 00000000..20d2beea --- /dev/null +++ b/codegen/htq/src/parser.rs @@ -0,0 +1,95 @@ +// Copyright 2024 Oxide Computer Company + +use std::collections::HashMap; + +use crate::{ + error::CodegenError, p4_type_to_htq_type, statement::emit_statement, + AsyncFlagAllocator, P4Context, RegisterAllocator, +}; +use htq::ast::Register; +use p4::{ast::ControlParameter, hlir::Hlir}; + +pub(crate) fn emit_parser_functions( + ast: &p4::ast::AST, + hlir: &Hlir, + afa: &mut AsyncFlagAllocator, +) -> Result, CodegenError> { + let mut result = Vec::new(); + + for parser in &ast.parsers { + let pf = emit_parser(ast, hlir, parser, afa)?; + result.extend(pf.into_iter()); + } + + Ok(result) +} + +fn emit_parser( + ast: &p4::ast::AST, + hlir: &Hlir, + parser: &p4::ast::Parser, + afa: &mut AsyncFlagAllocator, +) -> Result, CodegenError> { + let mut result = Vec::new(); + let mut parameters = Vec::new(); + let mut psub = HashMap::>::default(); + let mut ra = RegisterAllocator::default(); + + let mut return_signature = Vec::new(); + + for x in &parser.parameters { + let typ = p4_type_to_htq_type(&x.ty)?; + if x.direction.is_out() { + return_signature.push(typ.clone()); + } + let p = htq::ast::Parameter { + reg: htq::ast::Register::new(x.name.as_str()), + typ, + }; + ra.alloc(&p.reg.0); + parameters.push(p); + } + + parameters.push(htq::ast::Parameter { + reg: ra.alloc("offset"), + typ: htq::ast::Type::Unsigned(32), + }); + + let mut names = parser.names(); + + // TODO XXX parsers cannot have tables, this indicates broken abstractions + // around code generation control flow. + let mut table_context = HashMap::default(); + + for state in &parser.states { + let mut ra = ra.clone(); + // keeps track of register revisions for locals + let mut statements = Vec::default(); + let mut blocks = Vec::default(); + for s in &state.statements.statements { + let (stmts, blks) = emit_statement( + s, + ast, + &P4Context::Parser(parser), + hlir, + &mut names, + &mut ra, + afa, + &mut psub, + &mut table_context, + )?; + statements.extend(stmts); + blocks.extend(blks); + } + let f = htq::ast::Function { + name: format!("{}_{}", parser.name, state.name), + parameters: parameters.clone(), + statements, + blocks, + return_signature: return_signature.clone(), + }; + result.push(f); + } + + Ok(result) +} diff --git a/codegen/htq/src/statement.rs b/codegen/htq/src/statement.rs new file mode 100644 index 00000000..57efd0b2 --- /dev/null +++ b/codegen/htq/src/statement.rs @@ -0,0 +1,660 @@ +// Copyright 2024 Oxide Computer Company + +use std::collections::HashMap; + +use htq::ast::{Beq, Fset, Register, Rset, StatementBlock, Value}; +use p4::{ + ast::{ + BinOp, ControlParameter, DeclarationInfo, Direction, Expression, + ExpressionKind, Lvalue, NameInfo, UserDefinedType, + }, + hlir::Hlir, +}; + +use crate::{ + error::CodegenError, + expression::{emit_expression, emit_single_valued_expression}, + AsyncFlagAllocator, P4Context, RegisterAllocator, TableContext, +}; + +pub(crate) fn emit_statement( + stmt: &p4::ast::Statement, + ast: &p4::ast::AST, + context: &P4Context<'_>, + hlir: &Hlir, + names: &mut HashMap, + ra: &mut RegisterAllocator, + afa: &mut AsyncFlagAllocator, + psub: &mut HashMap>, + table_context: &mut TableContext, +) -> Result< + (Vec, Vec), + CodegenError, +> { + use p4::ast::Statement as S; + match stmt { + S::Empty => Ok((Vec::default(), Vec::default())), + S::Assignment(lval, expr) => { + let stmts = emit_assignment( + hlir, + ast, + context, + names, + lval, + expr, + ra, + afa, + psub, + table_context, + )?; + Ok((stmts, Vec::default())) + } + S::Call(call) => emit_call( + hlir, + ast, + context, + names, + call, + ra, + afa, + psub, + table_context, + ), + S::If(if_block) => emit_if_block( + hlir, + ast, + context, + names, + if_block, + ra, + afa, + psub, + table_context, + ), + S::Variable(v) => { + let stmts = + emit_variable(hlir, ast, context, names, v, ra, afa, psub)?; + Ok((stmts, Vec::default())) + } + S::Constant(_c) => Ok((Vec::default(), Vec::default())), //TODO + S::Transition(t) => emit_transition( + hlir, + ast, + context, + names, + t, + ra, + afa, + psub, + table_context, + ), + S::Return(_r) => Ok((Vec::default(), Vec::default())), //TODO + } +} + +fn emit_transition( + _hlir: &Hlir, + _ast: &p4::ast::AST, + context: &P4Context<'_>, + _names: &mut HashMap, + transition: &p4::ast::Transition, + ra: &mut RegisterAllocator, + _afa: &mut AsyncFlagAllocator, + _psub: &mut HashMap>, + _table_context: &mut TableContext, +) -> Result< + (Vec, Vec), + CodegenError, +> { + let parser = match &context { + P4Context::Parser(p) => *p, + P4Context::Control(_) => { + return Err(CodegenError::TransitionOutsideParser( + transition.clone(), + )) + } + }; + + let transition_to = match &transition { + p4::ast::Transition::Reference(lval) => lval, + p4::ast::Transition::Select(_) => todo!("transition select"), + }; + + let mut args = Vec::default(); + let mut targets = Vec::default(); + for x in &parser.parameters { + if x.direction.is_out() { + targets.push(ra.alloc(&x.name)); + } + args.push(Value::reg(ra.get(&x.name).ok_or( + CodegenError::NoRegisterForParameter(x.name.clone(), ra.clone()), + )?)); + } + args.push(Value::reg(ra.get("offset").ok_or( + CodegenError::NoRegisterForParameter( + String::from("offset"), + ra.clone(), + ), + )?)); + + let hdr = targets[0].clone(); + + let mut stmts = Vec::default(); + match transition_to.name.as_str() { + "accept" => { + stmts.push(htq::ast::Statement::SetValid(htq::ast::SetValid { + output: ra.alloc(&hdr.0), + target: hdr.clone(), + offsets: Vec::default(), + source: htq::ast::Value::bool(true), + })); + } + "reject" => { + stmts.push(htq::ast::Statement::SetValid(htq::ast::SetValid { + output: ra.alloc(&hdr.0), + target: hdr.clone(), + offsets: Vec::default(), + source: htq::ast::Value::bool(false), + })); + } + _ => { + stmts.push(htq::ast::Statement::Call(htq::ast::Call { + fname: format!("{}_{}", parser.name, transition_to.name), + args, + targets: targets.clone(), + })); + } + } + + stmts.push(htq::ast::Statement::Return(htq::ast::Return { + registers: targets, + })); + + Ok((stmts, Vec::default())) +} + +fn emit_if_block( + hlir: &Hlir, + ast: &p4::ast::AST, + context: &P4Context<'_>, + names: &mut HashMap, + iblk: &p4::ast::IfBlock, + ra: &mut RegisterAllocator, + afa: &mut AsyncFlagAllocator, + psub: &mut HashMap>, + table_context: &mut TableContext, +) -> Result< + (Vec, Vec), + CodegenError, +> { + let (mut statements, mut blocks) = emit_conditional_block( + hlir, + ast, + context, + names, + &iblk.predicate, + &iblk.block, + ra, + afa, + psub, + table_context, + )?; + + for elif in &iblk.else_ifs { + let (stmts, blks) = emit_conditional_block( + hlir, + ast, + context, + names, + &elif.predicate, + &elif.block, + ra, + afa, + psub, + table_context, + )?; + statements.extend(stmts); + blocks.extend(blks); + } + + Ok((statements, blocks)) +} + +fn emit_conditional_block( + hlir: &Hlir, + ast: &p4::ast::AST, + context: &P4Context<'_>, + names: &mut HashMap, + predicate: &p4::ast::Expression, + block: &p4::ast::StatementBlock, + ra: &mut RegisterAllocator, + afa: &mut AsyncFlagAllocator, + psub: &mut HashMap>, + table_context: &mut TableContext, +) -> Result< + (Vec, Vec), + CodegenError, +> { + let mut result = Vec::default(); + let mut blocks = Vec::default(); + + let (source, predicate) = + if let ExpressionKind::Binary(lhs, BinOp::Eq, rhs) = &predicate.kind { + let (source_statements, blks, source) = + emit_single_valued_expression( + lhs.as_ref(), + hlir, + ast, + context, + ra, + afa, + names, + table_context, + )?; + blocks.extend(blks); + result.extend(source_statements.clone()); + + let (predicate_statements, blks, predicate) = + emit_single_valued_expression( + rhs.as_ref(), + hlir, + ast, + context, + ra, + afa, + names, + table_context, + )?; + blocks.extend(blks); + result.extend(predicate_statements.clone()); + + (source, Value::reg(predicate)) + } else { + let (predicate_statements, blks, source) = + emit_single_valued_expression( + predicate, + hlir, + ast, + context, + ra, + afa, + names, + table_context, + )?; + blocks.extend(blks); + result.extend(predicate_statements.clone()); + (source, htq::ast::Value::bool(true)) + }; + + let params = ra.all_registers(); + let args = params.clone().into_iter().map(Value::reg).collect(); + let label = format!("{}_hit", source.0); + result.push(htq::ast::Statement::Beq(Beq { + source, + predicate, + label: label.clone(), + args, + })); + + // create a clean register allocator for the statements in the new block + let mut block_ra = ra.rebase(); + let block_params = block_ra.all_registers(); + + let mut blk = StatementBlock { + name: label, + parameters: block_params, + statements: Vec::default(), + }; + + for stmt in &block.statements { + // TODO nested if blocks + let (stmts, blks) = emit_statement( + stmt, + ast, + context, + hlir, + names, + &mut block_ra, + afa, + psub, + table_context, + )?; + blocks.extend(blks); + blk.statements.extend(stmts); + } + + if let P4Context::Control(control) = &context { + let mut out_params = Vec::default(); + for x in &control.parameters { + if x.ty.is_lookup_result() { + continue; + } + out_params.push(block_ra.get(&x.name).ok_or( + CodegenError::NoRegisterForParameter( + x.name.clone(), + block_ra.clone(), + ), + )?); + } + blk.statements + .push(htq::ast::Statement::Return(htq::ast::Return { + registers: out_params, + })); + } + + blocks.push(blk); + + Ok((result, blocks)) +} + +fn emit_variable( + _hlir: &Hlir, + _ast: &p4::ast::AST, + _context: &P4Context<'_>, + names: &mut HashMap, + var: &p4::ast::Variable, + ra: &mut RegisterAllocator, + _afa: &mut AsyncFlagAllocator, + _psub: &mut HashMap>, +) -> Result, CodegenError> { + //TODO(ry) it's unfortunate that a codegen module has to + // manually maintain scope information. Perhaps we should be using + // the AST visitor ... although I'm not sure if the AST visitor + // maintains a scope either, if not it probably should .... + names.insert( + var.name.clone(), + NameInfo { + ty: var.ty.clone(), + decl: DeclarationInfo::Local, + }, + ); + + if let Some(init) = &var.initializer { + if let ExpressionKind::Lvalue(lval) = &init.kind { + // TODO this could be modeled better (more explicitly) in the + // AST + if lval.leaf() == "await" { + return Ok(vec![htq::ast::Statement::Await(htq::ast::Await { + source: Value::reg( + // TODO this is extermely fragile relying on a + // register naming convention. + ra.get(&format!("{}_sync", lval.root())).unwrap(), + ), + })]); + } + } + } + + Ok(Vec::default()) +} + +fn emit_call( + hlir: &Hlir, + ast: &p4::ast::AST, + context: &P4Context<'_>, + names: &mut HashMap, + call: &p4::ast::Call, + ra: &mut RegisterAllocator, + afa: &mut AsyncFlagAllocator, + _psub: &mut HashMap>, + table_context: &mut TableContext, +) -> Result< + (Vec, Vec), + CodegenError, +> { + let (instrs, blocks, _result) = match &context { + P4Context::Control(c) => crate::expression::emit_call_in_control( + call, + c, + hlir, + ast, + context, + ra, + afa, + names, + table_context, + )?, + P4Context::Parser(p) => crate::expression::emit_call_in_parser( + call, + p, + hlir, + ast, + ra, + afa, + names, + table_context, + )?, + }; + Ok((instrs, blocks)) +} + +#[allow(clippy::too_many_arguments)] +fn emit_assignment( + hlir: &Hlir, + ast: &p4::ast::AST, + context: &P4Context<'_>, + names: &mut HashMap, + target: &p4::ast::Lvalue, + source: &p4::ast::Expression, + ra: &mut RegisterAllocator, + afa: &mut AsyncFlagAllocator, + psub: &mut HashMap>, + table_context: &mut TableContext, +) -> Result, CodegenError> { + // Things that can be assigned to are lvalues with the following + // declaration kinds. The table shows how each kind is referenced + // in htq code. + // + // *==========================================* + // | Decl Kind | Htq Kind | Instrs | + // |---------------+-------------+------------| + // | Parameter | Pointer | load/store | + // | Struct member | Pointer | fget/fset | + // | Header member | Pointer | fget/fset | + // | Local | Register(s) | arithmetic | + // *==========================================* + // + // Things that can be assigned from are expressions which have the + // following kinds. + // + // Concrete types + // + // *=================================* + // | Expr Kind | Htq Kind | + // |----------------------+----------| + // | Bool literal | bool | + // | Integer literal | i128 | + // | Bitfield Literal (N) | uN | + // | Signed Literal (N) | iN | + // *=================================* + // + // Types that need to be resolved that eventually decay to a concrete + // type. + // + // - lvalue + // - binary + // - index + // - slice + // - call + // - list + // + + let target_info = hlir + .lvalue_decls + .get(target) + .ok_or(CodegenError::UndefinedLvalue(target.clone()))?; + + // reg typ + // TODO is there an assignment that can result in blocks ....? + let (mut instrs, _blocks, expr_value) = emit_expression( + source, + hlir, + ast, + context, + ra, + afa, + names, + table_context, + )?; + + let expr_value = expr_value.ok_or( + CodegenError::AssignmentExpressionRequiresValue(source.clone()), + )?; + + if expr_value.registers.is_empty() { + return Err(CodegenError::AssignmentExpressionRequiresValue( + source.clone(), + )); + } + + match &target_info.decl { + DeclarationInfo::Parameter(Direction::Out) + | DeclarationInfo::Parameter(Direction::InOut) => { + if target_info.ty.is_lookup_result() { + if let P4Context::Control(control) = &context { + if let Some(param) = control.get_parameter(target.root()) { + psub.insert( + param.clone(), + expr_value + .registers + .iter() + .map(|x| ra.alloc_next(&x.0)) + .collect(), + ); + } + } + } + // TODO store instr + } + DeclarationInfo::StructMember | DeclarationInfo::HeaderMember => { + let treg = ra.get(target.root()).ok_or( + CodegenError::RegisterDoesNotExistForLval(target.clone()), + )?; + let output = ra.alloc(target.root()); + let offsets = member_offsets(ast, names, target)?; + let instr = Fset { + output, + offsets, + typ: expr_value.registers[0].1.clone(), + target: treg, + source: Value::Register(expr_value.registers[0].0.clone()), + }; + instrs.push(htq::ast::Statement::Fset(instr)); + } + DeclarationInfo::Local => { + let ty = hlir + .expression_types + .get(source) + .ok_or(CodegenError::UntypedExpression(source.clone()))?; + + //TODO(ry) it's unfortunate that a codegen module has to + // manually maintain scope information. Perhaps we should be using + // the AST visitor ... although I'm not sure if the AST visitor + // maintains a scope either, if not it probably should .... + names.insert( + target.name.clone(), + NameInfo { + ty: ty.clone(), + decl: DeclarationInfo::Local, + }, + ); + let target = ra.alloc(&target.name); + + let instr = Rset { + target, + typ: expr_value.registers[0].1.clone(), + source: Value::Register(expr_value.registers[0].0.clone()), + }; + instrs.push(htq::ast::Statement::Rset(instr)); + } + _ => { + return Err(CodegenError::InvalidAssignment( + target_info.decl.clone(), + )) + } + }; + + Ok(instrs) +} + +pub(crate) fn member_offsets( + ast: &p4::ast::AST, + names: &HashMap, + lval: &Lvalue, +) -> Result, CodegenError> { + let root = lval.root(); + let info = names.get(root).ok_or(CodegenError::MemberParentNotFound( + root.to_owned(), + lval.clone(), + ))?; + let mut offsets = Vec::default(); + member_offsets_rec(ast, &info.ty, &mut offsets, lval.clone())?; + Ok(offsets) +} + +fn member_offsets_rec( + ast: &p4::ast::AST, + ty: &p4::ast::Type, + offsets: &mut Vec, + mut lval: Lvalue, +) -> Result<(), CodegenError> { + match ty { + p4::ast::Type::UserDefined(name, _) => { + let root_type = ast.get_user_defined_type(name).ok_or( + CodegenError::UserDefinedTypeNotFound( + name.clone(), + lval.token.clone(), + ), + )?; + lval = lval.pop_left(); + let member = lval.root(); + //TODO + let (offset, member_ty) = match &root_type { + UserDefinedType::Struct(s) => { + let off = s.index_of(member).ok_or( + CodegenError::MemberOffsetNotFound(lval.clone()), + )?; + let member_info = + s.members.iter().find(|x| x.name == member).ok_or( + CodegenError::MemberNotFound( + member.to_owned(), + lval.clone(), + ), + )?; + (off, member_info.ty.clone()) + } + UserDefinedType::Header(h) => { + let off = h.index_of(member).ok_or( + CodegenError::MemberOffsetNotFound(lval.clone()), + )?; + let member_info = + h.members.iter().find(|x| x.name == member).ok_or( + CodegenError::MemberNotFound( + member.to_owned(), + lval.clone(), + ), + )?; + (off, member_info.ty.clone()) + } + UserDefinedType::Extern(e) => { + return Err(CodegenError::CannotOffsetExtern( + e.name.clone(), + lval.token.clone(), + )); + } + }; + let offset = i128::try_from(offset).map_err(|_| { + CodegenError::NumericConversion(Expression { + token: lval.token.clone(), + kind: ExpressionKind::Lvalue(lval.clone()), + }) + })?; + offsets.push(Value::number(offset)); + if lval.degree() > 1 { + member_offsets_rec(ast, &member_ty, offsets, lval.clone())?; + } + } + _ => return Err(CodegenError::ExpectedStructParent(lval.clone())), + } + Ok(()) +} diff --git a/codegen/htq/src/table.rs b/codegen/htq/src/table.rs new file mode 100644 index 00000000..288c86d9 --- /dev/null +++ b/codegen/htq/src/table.rs @@ -0,0 +1,48 @@ +// Copyright 2024 Oxide Computer Company + +use p4::hlir::Hlir; + +use crate::{error::CodegenError, p4_type_to_htq_type}; + +pub fn p4_match_kind_to_htq_match_kind( + p4m: &p4::ast::MatchKind, +) -> htq::ast::LookupType { + match p4m { + p4::ast::MatchKind::Exact => htq::ast::LookupType::Exact, + p4::ast::MatchKind::LongestPrefixMatch => htq::ast::LookupType::Lpm, + p4::ast::MatchKind::Range => htq::ast::LookupType::Range, + p4::ast::MatchKind::Ternary => htq::ast::LookupType::Ternary, + } +} + +pub(crate) fn p4_table_to_htq_table( + p4c: &p4::ast::Control, + p4t: &p4::ast::Table, + hlir: &Hlir, +) -> Result { + let mut action_args = Vec::new(); + for a in &p4t.actions { + let act = p4c.get_action(&a.name).unwrap(); + action_args.push( + act.parameters + .iter() + .map(|x| p4_type_to_htq_type(&x.ty)) + .collect::, CodegenError>>()?, + ); + } + Ok(htq::ast::Table { + name: p4t.name.clone(), + keyset: p4t + .key + .iter() + .map(|(lval, match_kind)| htq::ast::TableKey { + typ: p4_type_to_htq_type( + &hlir.lvalue_decls.get(lval).unwrap().ty, + ) + .unwrap(), + lookup_typ: p4_match_kind_to_htq_match_kind(match_kind), + }) + .collect(), + action_args, + }) +} diff --git a/codegen/rust/Cargo.toml b/codegen/rust/Cargo.toml index eb38fa3e..c0dbfa37 100644 --- a/codegen/rust/Cargo.toml +++ b/codegen/rust/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] p4.workspace = true +p4-cg.workspace = true quote.workspace = true proc-macro2.workspace = true syn.workspace = true diff --git a/codegen/rust/src/control.rs b/codegen/rust/src/control.rs index 6c1bbb36..76f4d73a 100644 --- a/codegen/rust/src/control.rs +++ b/codegen/rust/src/control.rs @@ -52,7 +52,7 @@ impl<'a> ControlGenerator<'a> { let control = cs.last().unwrap().1; let (_, mut param_types) = self.control_parameters(control); for var in &control.variables { - if let Type::UserDefined(typename) = &var.ty { + if let Type::UserDefined(typename, _) = &var.ty { if self.ast.get_extern(typename).is_some() { let extern_type = format_ident!("{}", typename); param_types.push(quote! { @@ -95,7 +95,7 @@ impl<'a> ControlGenerator<'a> { let qtn = qualified_table_function_name(None, &cs, table); let (_, mut param_types) = self.control_parameters(c); for var in &c.variables { - if let Type::UserDefined(typename) = &var.ty { + if let Type::UserDefined(typename, _) = &var.ty { if self.ast.get_extern(typename).is_some() { let extern_type = format_ident!("{}", typename); param_types.push(quote! { @@ -143,7 +143,7 @@ impl<'a> ControlGenerator<'a> { for arg in &control.parameters { match arg.ty { - Type::UserDefined(ref typename) => { + Type::UserDefined(ref typename, _) => { match self.ast.get_user_defined_type(typename) { Some(_udt) => { let name = format_ident!("{}", arg.name); @@ -202,7 +202,7 @@ impl<'a> ControlGenerator<'a> { let (mut params, _) = self.control_parameters(control); for var in &control.variables { - if let Type::UserDefined(typename) = &var.ty { + if let Type::UserDefined(typename, _) = &var.ty { if self.ast.get_extern(typename).is_some() { let name = format_ident!("{}", var.name); let extern_type = format_ident!("{}", typename); @@ -235,7 +235,7 @@ impl<'a> ControlGenerator<'a> { for arg in &action.parameters { // if the type is user defined, check to ensure it's defined - if let Type::UserDefined(ref typename) = arg.ty { + if let Type::UserDefined(ref typename, _) = arg.ty { match self.ast.get_user_defined_type(typename) { Some(_) => { let name = format_ident!("{}", arg.name); @@ -490,7 +490,7 @@ impl<'a> ControlGenerator<'a> { for var in &control.variables { //TODO check in checker that externs are actually defined by //SoftNPU. - if let Type::UserDefined(typename) = &var.ty { + if let Type::UserDefined(typename, _) = &var.ty { if self.ast.get_extern(typename).is_some() { let name = format_ident!("{}", var.name); let extern_type = format_ident!("{}", typename); diff --git a/codegen/rust/src/header.rs b/codegen/rust/src/header.rs index d36f17be..289f178e 100644 --- a/codegen/rust/src/header.rs +++ b/codegen/rust/src/header.rs @@ -1,7 +1,7 @@ // Copyright 2022 Oxide Computer Company -use crate::{rust_type, type_size, Context}; -use p4::ast::{Header, AST}; +use crate::{rust_type, Context}; +use p4::ast::{type_size, Header, AST}; use quote::{format_ident, quote}; pub(crate) struct HeaderGenerator<'a> { diff --git a/codegen/rust/src/lib.rs b/codegen/rust/src/lib.rs index a41ba0e7..4b78525a 100644 --- a/codegen/rust/src/lib.rs +++ b/codegen/rust/src/lib.rs @@ -10,7 +10,7 @@ use quote::{format_ident, quote}; use p4::ast::{ ActionParameter, Control, ControlParameter, DeclarationInfo, Direction, Expression, ExpressionKind, HeaderMember, Lvalue, MutVisitor, NameInfo, - Parser, StructMember, Table, Type, UserDefinedType, AST, + Parser, StructMember, Table, Type, AST, }; use p4::hlir::Hlir; use p4::util::resolve_lvalue; @@ -218,7 +218,8 @@ fn rust_type(ty: &Type) -> TokenStream { Type::Int(_size) => todo!("generate int type"), Type::Varbit(_size) => todo!("generate varbit type"), Type::String => quote! { String }, - Type::UserDefined(name) => { + //TODO generic types + Type::UserDefined(name, _) => { let typename = format_ident!("{}", name); quote! { #typename } } @@ -241,70 +242,10 @@ fn rust_type(ty: &Type) -> TokenStream { Type::Action => { todo!("rust type for action"); } + Type::Sync(_) => todo!("rust codegen for sync"), } } -fn type_size(ty: &Type, ast: &AST) -> usize { - match ty { - Type::Bool => 1, - Type::Error => todo!("generate error size"), - Type::Bit(size) => *size, - Type::Int(size) => *size, - Type::Varbit(size) => *size, - Type::String => todo!("generate string size"), - Type::UserDefined(name) => { - let mut sz: usize = 0; - let udt = ast.get_user_defined_type(name).unwrap_or_else(|| { - panic!("expect user defined type: {}", name) - }); - - match udt { - UserDefinedType::Struct(s) => { - for m in &s.members { - sz += type_size(&m.ty, ast); - } - sz - } - UserDefinedType::Header(h) => { - for m in &h.members { - sz += type_size(&m.ty, ast); - } - sz - } - UserDefinedType::Extern(_) => { - todo!("size for extern?"); - } - } - } - Type::ExternFunction => { - todo!("type size for extern function"); - } - Type::HeaderMethod => { - todo!("type size for header method"); - } - Type::Table => { - todo!("type size for table"); - } - Type::Void => 0, - Type::List(_) => todo!("type size for list"), - Type::State => { - todo!("type size for state"); - } - Type::Action => { - todo!("type size for action"); - } - } -} - -fn type_size_bytes(ty: &Type, ast: &AST) -> usize { - let s = type_size(ty, ast); - let mut b = s >> 3; - if s % 8 != 0 { - b += 1 - } - b -} - // in the case of an expression // // a &&& b @@ -332,7 +273,7 @@ fn is_header( ) -> bool { //TODO: get from hlir? let typename = match resolve_lvalue(lval, ast, names).unwrap().ty { - Type::UserDefined(name) => name, + Type::UserDefined(name, _) => name, _ => return false, }; ast.get_header(&typename).is_some() diff --git a/codegen/rust/src/p4struct.rs b/codegen/rust/src/p4struct.rs index 83807e96..1d4474b2 100644 --- a/codegen/rust/src/p4struct.rs +++ b/codegen/rust/src/p4struct.rs @@ -32,7 +32,8 @@ impl<'a> StructGenerator<'a> { let name = format_ident!("{}", member.name); let name_s = &member.name; match &member.ty { - Type::UserDefined(ref typename) => { + //TODO generics + Type::UserDefined(ref typename, _) => { if self.ast.get_header(typename).is_some() { let ty = format_ident!("{}", typename); diff --git a/codegen/rust/src/pipeline.rs b/codegen/rust/src/pipeline.rs index 326f1ee7..6f6bf5d7 100644 --- a/codegen/rust/src/pipeline.rs +++ b/codegen/rust/src/pipeline.rs @@ -1,11 +1,12 @@ // Copyright 2022 Oxide Computer Company use crate::{ - qualified_table_function_name, qualified_table_name, rust_type, - type_size_bytes, Context, Settings, + qualified_table_function_name, qualified_table_name, rust_type, Context, + Settings, }; use p4::ast::{ - Control, Direction, MatchKind, PackageInstance, Parser, Table, Type, AST, + type_size_bytes, Control, Direction, MatchKind, PackageInstance, Parser, + Table, Type, AST, }; use p4::hlir::Hlir; use proc_macro2::TokenStream; @@ -486,7 +487,8 @@ impl<'a> PipelineGenerator<'a> { let (_, mut param_types) = cg.control_parameters(table_control); for var in &table_control.variables { - if let Type::UserDefined(typename) = &var.ty { + //TODO(generics) + if let Type::UserDefined(typename, _) = &var.ty { if self.ast.get_extern(typename).is_some() { let extern_type = format_ident!("{}", typename); param_types.push(quote! { @@ -807,7 +809,7 @@ impl<'a> PipelineGenerator<'a> { Type::String => { todo!(); } - Type::UserDefined(_s) => { + Type::UserDefined(_s, _) => { todo!(); } Type::ExternFunction => { @@ -825,6 +827,9 @@ impl<'a> PipelineGenerator<'a> { Type::List(_) => { todo!(); } + Type::Sync(_) => { + todo!(); + } } } let mut control_params = Vec::new(); @@ -858,7 +863,8 @@ impl<'a> PipelineGenerator<'a> { for var in &control.variables { let name = format_ident!("{}", var.name); - if let Type::UserDefined(typename) = &var.ty { + //TODO(generics) + if let Type::UserDefined(typename, _) = &var.ty { if self.ast.get_extern(typename).is_some() { control_params.push(quote! { #name }); let extern_type = format_ident!("{}", typename); @@ -964,7 +970,8 @@ impl<'a> PipelineGenerator<'a> { for var in &control.variables { let name = format_ident!("{}", var.name); - if let Type::UserDefined(typename) = &var.ty { + //TODO(generics) + if let Type::UserDefined(typename, _) = &var.ty { if self.ast.get_extern(typename).is_some() { control_params.push(quote! { #name }); let extern_type = format_ident!("{}", typename); diff --git a/codegen/rust/src/statement.rs b/codegen/rust/src/statement.rs index 1537993f..5911f904 100644 --- a/codegen/rust/src/statement.rs +++ b/codegen/rust/src/statement.rs @@ -88,7 +88,7 @@ impl<'a> StatementGenerator<'a> { // TODO eww, to better to figure out precisely when to_owned // and clone are needed quote! { #rhs.to_owned().clone() } - } else if let Type::UserDefined(_) = rhs_ty { + } else if let Type::UserDefined(_, _) = rhs_ty { quote! { #rhs.clone() } } else { rhs @@ -394,7 +394,7 @@ impl<'a> StatementGenerator<'a> { }); let control_instance = match &name_info.ty { - Type::UserDefined(name) => { + Type::UserDefined(name, _) => { self.ast.get_control(name).unwrap_or_else(|| { panic!("codegen: control {} not found in AST", name,) }) @@ -526,7 +526,7 @@ impl<'a> StatementGenerator<'a> { for var in &control.variables { let name = format_ident!("{}", var.name); - if let Type::UserDefined(typename) = &var.ty { + if let Type::UserDefined(typename, _) = &var.ty { if self.ast.get_extern(typename).is_some() { action_args.push(quote! { &#name }); } diff --git a/p4/src/ast.rs b/p4/src/ast.rs index 19db75f5..ed8318dd 100644 --- a/p4/src/ast.rs +++ b/p4/src/ast.rs @@ -7,6 +7,7 @@ use std::hash::{Hash, Hasher}; use crate::lexer::Token; +/// An abstract syntax tree for the P4 language. #[derive(Debug, Default)] pub struct AST { pub constants: Vec, @@ -217,7 +218,7 @@ impl PackageInstance { #[derive(Debug)] pub struct Package { pub name: String, - pub type_parameters: Vec, + pub type_parameters: Vec, pub parameters: Vec, } @@ -262,7 +263,7 @@ impl Package { #[derive(Debug)] pub struct PackageParameter { pub type_name: String, - pub type_parameters: Vec, + pub type_parameters: Vec, pub name: String, } @@ -292,7 +293,7 @@ impl PackageParameter { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Type { Bool, Error, @@ -300,7 +301,7 @@ pub enum Type { Varbit(usize), Int(usize), String, - UserDefined(String), + UserDefined(String, Vec>), ExternFunction, //TODO actual signature Table, Void, @@ -308,6 +309,7 @@ pub enum Type { State, Action, HeaderMethod, + Sync(Box), } impl Type { @@ -346,6 +348,18 @@ impl Type { } } } + + pub fn is_lookup_result(&self) -> bool { + match self { + Self::UserDefined(s, _) if s == "lookup_result" => true, + Self::Sync(typ) => typ.is_lookup_result(), + _ => false, + } + } + + pub fn is_sync(&self) -> bool { + matches!(self, Self::Sync(_)) + } } impl fmt::Display for Type { @@ -357,7 +371,22 @@ impl fmt::Display for Type { Type::Varbit(size) => write!(f, "varbit<{}>", size), Type::Int(size) => write!(f, "int<{}>", size), Type::String => write!(f, "string"), - Type::UserDefined(name) => write!(f, "{}", name), + Type::UserDefined(name, params) => { + if params.is_empty() { + write!(f, "{}", name) + } else { + write!( + f, + "{}<{}>", + name, + params + .iter() + .map(|x| x.to_string()) + .collect::>() + .join(",") + ) + } + } Type::ExternFunction => write!(f, "extern function"), Type::Table => write!(f, "table"), Type::Void => write!(f, "void"), @@ -371,6 +400,7 @@ impl fmt::Display for Type { } write!(f, ">") } + Type::Sync(param) => write!(f, "sync<{param}>"), } } } @@ -765,6 +795,26 @@ impl Header { m.mut_accept_mut(v); } } + + pub fn index_of(&self, member_name: &str) -> Option { + for (i, m) in self.members.iter().enumerate() { + if m.name == member_name { + return Some(i); + } + } + None + } + + pub fn offset_of(&self, ast: &AST, member_name: &str) -> Option { + let mut offset = 0; + for m in &self.members { + if m.name == member_name { + return Some(offset); + } + offset += type_size(&m.ty, ast); + } + None + } } #[derive(Debug, Clone)] @@ -850,6 +900,26 @@ impl Struct { m.mut_accept_mut(v); } } + + pub fn index_of(&self, member_name: &str) -> Option { + for (i, m) in self.members.iter().enumerate() { + if m.name == member_name { + return Some(i); + } + } + None + } + + pub fn offset_of(&self, ast: &AST, member_name: &str) -> Option { + let mut offset = 0; + for m in &self.members { + if m.name == member_name { + return Some(offset); + } + offset += type_size(&m.ty, ast); + } + None + } } #[derive(Debug, Clone)] @@ -886,7 +956,7 @@ pub struct Control { pub name: String, pub variables: Vec, pub constants: Vec, - pub type_parameters: Vec, + pub type_parameters: Vec, pub parameters: Vec, pub actions: Vec, pub tables: Vec, @@ -942,7 +1012,7 @@ impl Control { result.push((chain.clone(), table)); } for v in &self.variables { - if let Type::UserDefined(typename) = &v.ty { + if let Type::UserDefined(typename, _) = &v.ty { if let Some(control_inst) = ast.get_control(typename) { result.extend_from_slice(&control_inst.tables_rec( ast, @@ -957,7 +1027,7 @@ impl Control { pub fn is_type_parameter(&self, name: &str) -> bool { for t in &self.type_parameters { - if t == name { + if t.to_string() == name { return true; } } @@ -1101,6 +1171,136 @@ impl Control { s.mut_accept_mut(v); } } + + pub fn resolve_lookup_result_args_size( + &self, + parameter_name: &str, + ast: &AST, + ) -> Option { + self.resolve_lookup_result_args_size_rec( + parameter_name, + ast, + &self.apply, + ) + } + + //TODO Does this need to consider the possibility of multiple assignments? + // Should that be an error? + pub fn resolve_lookup_result_args_size_rec( + &self, + parameter_name: &str, + ast: &AST, + block: &StatementBlock, + ) -> Option { + let mut table: Option
= None; + let mut size = 0; + let mut found = false; + for x in &block.statements { + match x { + Statement::Assignment(lval, expr) => { + if lval.root() != parameter_name { + continue; + } + match &expr.kind { + ExpressionKind::Call(call) => { + let mut lv = call.lval.clone(); + if lv.leaf() == "await" { + lv = lv.pop_right(); + } + if lv.leaf() != "apply" { + continue; + } + if let Some(tbl) = self.get_table(call.lval.root()) + { + size = usize::max( + self.maximum_action_arg_length_for_table( + ast, tbl, + ), + size, + ); + table = Some(tbl.clone()); + found = true; + } else { + continue; + } + } + _ => continue, + } + } + Statement::If(if_block) => { + if let Some(info) = self + .resolve_lookup_result_args_size_rec( + parameter_name, + ast, + &if_block.block, + ) + { + found = true; + size = usize::max(size, info.max_arg_size); + table = Some(info.table); + } + for x in &if_block.else_ifs { + if let Some(info) = self + .resolve_lookup_result_args_size_rec( + parameter_name, + ast, + &x.block, + ) + { + found = true; + size = usize::max(size, info.max_arg_size); + table = Some(info.table); + } + } + if let Some(else_block) = &if_block.else_block { + if let Some(info) = self + .resolve_lookup_result_args_size_rec( + parameter_name, + ast, + else_block, + ) + { + found = true; + size = usize::max(size, info.max_arg_size); + table = Some(info.table); + } + } + } + _ => continue, + } + } + if found { + Some(TableInfo { + max_arg_size: size, + table: table.unwrap(), + }) + } else { + None + } + } + + pub fn maximum_action_arg_length_for_table( + &self, + ast: &AST, + table: &Table, + ) -> usize { + let mut size = 0; + for action in &table.actions { + let mut asize = 0; + let action = self.get_action(&action.name).unwrap(); + for p in &action.parameters { + let psize = type_size(&p.ty, ast); + asize += psize; + } + size = usize::max(size, asize); + } + size + } +} + +pub struct TableInfo { + pub table: Table, + pub max_arg_size: usize, } impl PartialEq for Control { @@ -1112,7 +1312,7 @@ impl PartialEq for Control { #[derive(Debug, Clone)] pub struct Parser { pub name: String, - pub type_parameters: Vec, + pub type_parameters: Vec, pub parameters: Vec, pub states: Vec, pub decl_only: bool, @@ -1135,7 +1335,7 @@ impl Parser { pub fn is_type_parameter(&self, name: &str) -> bool { for t in &self.type_parameters { - if t == name { + if t.to_string() == name { return true; } } @@ -1210,7 +1410,7 @@ impl Parser { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Hash, Eq)] pub struct ControlParameter { pub direction: Direction, pub ty: Type, @@ -1243,7 +1443,7 @@ impl ControlParameter { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Direction { In, Out, @@ -1251,6 +1451,15 @@ pub enum Direction { Unspecified, } +impl Direction { + pub fn is_out(&self) -> bool { + *self == Self::Out || *self == Self::InOut + } + pub fn is_in(&self) -> bool { + *self == Self::In || *self == Self::InOut + } +} + #[derive(Debug, Clone, Default)] pub struct StatementBlock { pub statements: Vec, @@ -1376,10 +1585,11 @@ pub struct Table { pub const_entries: Vec, pub size: usize, pub token: Token, + pub is_async: bool, } impl Table { - pub fn new(name: String, token: Token) -> Self { + pub fn new(name: String, is_async: bool, token: Token) -> Self { Self { name, actions: Vec::new(), @@ -1387,6 +1597,7 @@ impl Table { key: Vec::new(), const_entries: Vec::new(), size: 0, + is_async, token, } } @@ -1886,6 +2097,7 @@ impl ElseIfBlock { pub struct Call { pub lval: Lvalue, pub args: Vec>, + pub with_await: bool, } impl Call { @@ -1970,6 +2182,25 @@ impl Lvalue { }, } } + pub fn pop_await(&self) -> Self { + let parts = self.parts(); + Lvalue { + name: if parts.len() == 1 { + parts[0].to_owned() + } else if parts.last() == Some(&"await") { + parts[..parts.len() - 1].join(".") + } else { + parts.join(".") + }, + token: Token { + kind: self.token.kind.clone(), + line: self.token.line, + col: self.token.col, + file: self.token.file.clone(), + }, + } + } + fn accept(&self, v: &V) { v.lvalue(self); } @@ -2215,7 +2446,7 @@ impl Extern { pub struct ExternMethod { pub return_type: Type, pub name: String, - pub type_parameters: Vec, + pub type_parameters: Vec, pub parameters: Vec, } @@ -2428,3 +2659,65 @@ pub trait MutVisitorMut { fn package_parameter(&mut self, _: &mut PackageParameter) {} fn extern_method(&mut self, _: &mut ExternMethod) {} } + +pub fn type_size(ty: &Type, ast: &AST) -> usize { + match ty { + Type::Bool => 1, + Type::Error => todo!("generate error size"), + Type::Bit(size) => *size, + Type::Int(size) => *size, + Type::Varbit(size) => *size, + Type::String => todo!("generate string size"), + Type::UserDefined(name, _) => { + let mut sz: usize = 0; + let udt = ast.get_user_defined_type(name).unwrap_or_else(|| { + panic!("expect user defined type: {}", name) + }); + + match udt { + UserDefinedType::Struct(s) => { + for m in &s.members { + sz += type_size(&m.ty, ast); + } + sz + } + UserDefinedType::Header(h) => { + for m in &h.members { + sz += type_size(&m.ty, ast); + } + sz + } + UserDefinedType::Extern(_) => { + todo!("size for extern?"); + } + } + } + Type::ExternFunction => { + todo!("type size for extern function"); + } + Type::HeaderMethod => { + todo!("type size for header method"); + } + Type::Table => { + todo!("type size for table"); + } + Type::Void => 0, + Type::List(_) => todo!("type size for list"), + Type::State => { + todo!("type size for state"); + } + Type::Action => { + todo!("type size for action"); + } + Type::Sync(_) => todo!("type size for sync"), + } +} + +pub fn type_size_bytes(ty: &Type, ast: &AST) -> usize { + let s = type_size(ty, ast); + let mut b = s >> 3; + if s % 8 != 0 { + b += 1 + } + b +} diff --git a/p4/src/check.rs b/p4/src/check.rs index 443ff06f..8b2ae431 100644 --- a/p4/src/check.rs +++ b/p4/src/check.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use crate::ast::{ Call, Control, DeclarationInfo, Expression, ExpressionKind, Header, Lvalue, NameInfo, Parser, State, Statement, StatementBlock, Struct, Table, - Transition, Type, VisitorMut, AST, + Transition, Type, Variable, VisitorMut, AST, }; use crate::hlir::{Hlir, HlirGenerator}; use crate::lexer::Token; @@ -96,7 +96,7 @@ impl ControlChecker { pub fn check_params(c: &Control, ast: &AST, diags: &mut Diagnostics) { for p in &c.parameters { - if let Type::UserDefined(typename) = &p.ty { + if let Type::UserDefined(typename, _) = &p.ty { if ast.get_user_defined_type(typename).is_none() { diags.push(Diagnostic { level: Level::Error, @@ -140,7 +140,7 @@ impl ControlChecker { pub fn check_variables(c: &Control, ast: &AST, diags: &mut Diagnostics) { for v in &c.variables { - if let Type::UserDefined(typename) = &v.ty { + if let Type::UserDefined(typename, _) = &v.ty { if ast.get_user_defined_type(typename).is_some() { continue; } @@ -203,6 +203,7 @@ impl ControlChecker { ast, hlir, diags, + local_values: HashMap::new(), }; c.accept_mut(&mut apc); } @@ -218,7 +219,7 @@ fn check_statement_block( for s in &block.statements { match s { Statement::Assignment(lval, xpr) => { - let name_info = match hlir.lvalue_decls.get(lval) { + let name_info = match hlir.lvalue_decls.get(&lval.pop_await()) { Some(info) => info, None => { diags.push(Diagnostic { @@ -261,7 +262,7 @@ fn check_statement_block( Statement::Empty => {} Statement::Call(c) if in_action => { let lval = c.lval.pop_right(); - let name_info = match hlir.lvalue_decls.get(&lval) { + let name_info = match hlir.lvalue_decls.get(&lval.pop_await()) { Some(info) => info, None => { diags.push(Diagnostic { @@ -285,7 +286,7 @@ fn check_statement_block( token: c.lval.token.clone(), }); } - Type::UserDefined(name) => { + Type::UserDefined(name, _) => { if ast.get_control(name).is_some() { diags.push(Diagnostic { level: Level::Error, @@ -311,26 +312,40 @@ pub struct ApplyCallChecker<'a> { ast: &'a AST, hlir: &'a Hlir, diags: &'a mut Diagnostics, + local_values: HashMap, } impl VisitorMut for ApplyCallChecker<'_> { + fn variable(&mut self, variable: &Variable) { + self.local_values.insert( + variable.name.clone(), + NameInfo { + ty: variable.ty.clone(), + decl: DeclarationInfo::Local, + }, + ); + } + fn call(&mut self, call: &Call) { let name = call.lval.root(); let names = self.c.names(); let name_info = match names.get(name) { Some(info) => info, - None => { - self.diags.push(Diagnostic { - level: Level::Error, - message: format!("{} is undefined", name), - token: call.lval.token.clone(), - }); - return; - } + None => match self.local_values.get(name) { + Some(info) => info, + None => { + self.diags.push(Diagnostic { + level: Level::Error, + message: format!("{} is undefined", name), + token: call.lval.token.clone(), + }); + return; + } + }, }; match &name_info.ty { - Type::UserDefined(name) => { + Type::UserDefined(name, _) => { if let Some(ctl) = self.ast.get_control(name) { self.check_apply_ctl_apply(call, ctl) } @@ -484,7 +499,7 @@ impl StructChecker { pub fn check(s: &Struct, ast: &AST) -> Diagnostics { let mut diags = Diagnostics::new(); for m in &s.members { - if let Type::UserDefined(typename) = &m.ty { + if let Type::UserDefined(typename, _) = &m.ty { if ast.get_user_defined_type(typename).is_none() { diags.push(Diagnostic { level: Level::Error, @@ -507,7 +522,7 @@ impl HeaderChecker { pub fn check(h: &Header, ast: &AST) -> Diagnostics { let mut diags = Diagnostics::new(); for m in &h.members { - if let Type::UserDefined(typename) = &m.ty { + if let Type::UserDefined(typename, _) = &m.ty { if ast.get_user_defined_type(typename).is_none() { diags.push(Diagnostic { level: Level::Error, @@ -868,7 +883,7 @@ fn check_lvalue( }); } } - Type::UserDefined(name) => { + Type::UserDefined(name, _) => { // get the parent type definition from the AST and check for the // referenced member if let Some(parent) = ast.get_struct(&name) { @@ -948,6 +963,19 @@ fn check_lvalue( }); } } + Type::Sync(inner_type) => { + if parts.len() > 1 && parts[1] != "await" { + diags.push(Diagnostic { + level: Level::Error, + message: format!( + "type {} does not have a member {}", + format!("sync<{}>", inner_type).bright_blue(), + parts[1].bright_blue(), + ), + token: lval.token.clone(), + }); + } + } }; diags } diff --git a/p4/src/hlir.rs b/p4/src/hlir.rs index 979cefd9..4b18cc10 100644 --- a/p4/src/hlir.rs +++ b/p4/src/hlir.rs @@ -195,7 +195,7 @@ impl<'a> HlirGenerator<'a> { } // TODO check extern methods in checker before getting here if let Some(name_info) = names.get(call.lval.root()) { - if let Type::UserDefined(typename) = &name_info.ty { + if let Type::UserDefined(typename, _) = &name_info.ty { if let Some(ext) = self.ast.get_extern(typename) { if let Some(m) = ext.get_method(call.lval.leaf()) { self.hlir @@ -335,7 +335,7 @@ impl<'a> HlirGenerator<'a> { }); None } - Type::UserDefined(_) => { + Type::UserDefined(_, _) => { self.diags.push(Diagnostic { level: Level::Error, message: "cannot index a user defined type".into(), @@ -367,6 +367,14 @@ impl<'a> HlirGenerator<'a> { }); None } + Type::Sync(inner) => { + self.diags.push(Diagnostic { + level: Level::Error, + message: format!("cannot index a sync<{inner}>"), + token: lval.token.clone(), + }); + None + } } } @@ -441,11 +449,11 @@ impl<'a> HlirGenerator<'a> { lval: &Lvalue, names: &mut HashMap, ) -> Option { - match resolve_lvalue(lval, self.ast, names) { + match resolve_lvalue(&lval.pop_await(), self.ast, names) { Ok(name_info) => { self.hlir .lvalue_decls - .insert(lval.clone(), name_info.clone()); + .insert(lval.pop_await(), name_info.clone()); Some(name_info.ty) } Err(e) => { diff --git a/p4/src/lexer.rs b/p4/src/lexer.rs index 02679d41..446a74ac 100644 --- a/p4/src/lexer.rs +++ b/p4/src/lexer.rs @@ -38,6 +38,8 @@ pub enum Kind { If, Else, Return, + Async, + Await, // // types @@ -48,6 +50,7 @@ pub enum Kind { Varbit, Int, String, + Sync, // // lexical elements @@ -166,6 +169,8 @@ impl fmt::Display for Kind { Kind::If => write!(f, "keyword if"), Kind::Else => write!(f, "keyword else"), Kind::Return => write!(f, "keyword return"), + Kind::Async => write!(f, "keyword async"), + Kind::Await => write!(f, "keyword await"), // // types @@ -176,6 +181,7 @@ impl fmt::Display for Kind { Kind::Varbit => write!(f, "type varbit"), Kind::Int => write!(f, "type int"), Kind::String => write!(f, "type string"), + Kind::Sync => write!(f, "type sync"), // // lexical elements @@ -316,8 +322,8 @@ impl<'a> Lexer<'a> { }); } - while self.skip_whitespace() {} - while self.skip_comment() {} + while self.skip_whitespace_or_comment() {} + //while self.skip_comment() {} if self.line >= self.lines.len() { return Ok(Token { kind: Kind::Eof, @@ -326,8 +332,7 @@ impl<'a> Lexer<'a> { file: self.file.clone(), }); } - self.skip_whitespace(); - //self.skip_comment(); + self.skip_whitespace_or_comment(); if let Some(t) = self.match_token("#include", Kind::PoundInclude) { return Ok(t); @@ -389,6 +394,14 @@ impl<'a> Lexer<'a> { return Ok(t); } + if let Some(t) = self.match_token("async", Kind::Async) { + return Ok(t); + } + + if let Some(t) = self.match_token("await", Kind::Await) { + return Ok(t); + } + if let Some(t) = self.match_token("&&", Kind::LogicalAnd) { return Ok(t); } @@ -529,6 +542,10 @@ impl<'a> Lexer<'a> { return Ok(t); } + if let Some(t) = self.match_token("sync", Kind::Sync) { + return Ok(t); + } + if let Some(t) = self.match_token("typedef", Kind::Typedef) { return Ok(t); } @@ -852,6 +869,10 @@ impl<'a> Lexer<'a> { false } + fn skip_whitespace_or_comment(&mut self) -> bool { + self.skip_whitespace() || self.skip_comment() + } + fn skip_block_comment(&mut self) { let mut chars = self.cursor.chars(); match chars.next() { diff --git a/p4/src/parser.rs b/p4/src/parser.rs index 61cfd5a2..d9884e3b 100644 --- a/p4/src/parser.rs +++ b/p4/src/parser.rs @@ -74,6 +74,23 @@ impl<'a> Parser<'a> { Ok(()) } + fn try_token(&mut self, expected: lexer::Kind) -> Result<(), Error> { + let token = self.next_token()?; + if token.kind != expected { + self.backlog.push(token.clone()); + return Err(ParserError { + at: token.clone(), + message: format!( + "Found {} expected '{}'.", + token.kind, expected, + ), + source: self.lexer.lines[token.line].into(), + } + .into()); + } + Ok(()) + } + fn parse_identifier( &mut self, what: &str, @@ -82,6 +99,10 @@ impl<'a> Parser<'a> { Ok(( match token.kind { Kind::Identifier(ref name) => name.into(), + Kind::Await => { + //TODO this is a bit of a hack + "await".into() + } Kind::Apply => { // sometimes apply is not the keyword but a method called // against tables. @@ -148,9 +169,30 @@ impl<'a> Parser<'a> { } lexer::Kind::Identifier(name) => { - Type::UserDefined(name.clone()) + let params = match self.parse_type_parameters() { + Ok(params) => params, + Err(_) => Vec::new(), + }; + Type::UserDefined( + name.clone(), + params.into_iter().map(Box::new).collect(), + ) + } + lexer::Kind::Sync => { + let params = self.parse_type_parameters()?; + if params.len() != 1 { + return Err(ParserError { + at: token.clone(), + message: format!( + "Expected exactly one type parameter, found {}", + params.len(), + ), + source: self.lexer.lines[token.line].into(), + } + .into()); + } + Type::Sync(Box::new(params[0].clone())) } - _ => { return Err( ParserError { @@ -390,6 +432,12 @@ impl<'a> Parser<'a> { } } + pub fn parse_await(&mut self) -> Result<(), Error> { + self.try_token(lexer::Kind::Dot)?; + self.try_token(lexer::Kind::Await)?; + Ok(()) + } + // parse a tuple of expressions (, ...), used for both tuples // and function call sites pub fn parse_expr_parameters( @@ -474,6 +522,7 @@ impl<'a> Parser<'a> { | lexer::Kind::Error | lexer::Kind::Bit | lexer::Kind::Int + | lexer::Kind::Sync | lexer::Kind::String => { self.backlog.push(token); let var = self.parse_variable()?; @@ -487,9 +536,24 @@ impl<'a> Parser<'a> { result.statements.push(Statement::Constant(c)); } - lexer::Kind::Identifier(_) - | lexer::Kind::If - | lexer::Kind::Return => { + lexer::Kind::Identifier(_) => { + if let Ok(peek) = self.next_token() { + if let lexer::Kind::Identifier(_) = &peek.kind { + self.backlog.push(peek); + self.backlog.push(token); + let var = self.parse_variable()?; + result.statements.push(Statement::Variable(var)); + continue; + } else { + self.backlog.push(peek); + } + } + self.backlog.push(token); + let mut sp = StatementParser::new(self); + let stmt = sp.run()?; + result.statements.push(stmt); + } + lexer::Kind::If | lexer::Kind::Return => { // push the identifier token into the backlog and run the // statement parser self.backlog.push(token); @@ -598,14 +662,15 @@ impl<'a> Parser<'a> { Ok(result) } - pub fn parse_type_parameters(&mut self) -> Result, Error> { + pub fn parse_type_parameters(&mut self) -> Result, Error> { let mut result = Vec::new(); - self.expect_token(lexer::Kind::AngleOpen)?; + self.try_token(lexer::Kind::AngleOpen)?; loop { - let (ident, _) = self.parse_identifier("type parameter name")?; - result.push(ident); + //let (ident, _) = self.parse_identifier("type parameter name")?; + let (ty, _) = self.parse_type()?; + result.push(ty); let token = self.next_token()?; match token.kind { @@ -1023,12 +1088,20 @@ impl<'a, 'b> ControlParser<'a, 'b> { // iterate over body statements loop { + let token = self.parser.next_token()?; + let is_async = if token.kind == lexer::Kind::Async { + true + } else { + self.parser.backlog.push(token); + false + }; + let token = self.parser.next_token()?; match token.kind { lexer::Kind::CurlyClose => break, lexer::Kind::Action => self.parse_action(control)?, - lexer::Kind::Table => self.parse_table(control)?, + lexer::Kind::Table => self.parse_table(control, is_async)?, lexer::Kind::Apply => self.parse_apply(control)?, lexer::Kind::Const => { let c = self.parser.parse_constant()?; @@ -1069,8 +1142,12 @@ impl<'a, 'b> ControlParser<'a, 'b> { Ok(()) } - pub fn parse_table(&mut self, control: &mut Control) -> Result<(), Error> { - let mut tp = TableParser::new(self.parser); + pub fn parse_table( + &mut self, + control: &mut Control, + is_async: bool, + ) -> Result<(), Error> { + let mut tp = TableParser::new(self.parser, is_async); let table = tp.run()?; control.tables.push(table); @@ -1166,16 +1243,17 @@ impl<'a, 'b> ActionParser<'a, 'b> { pub struct TableParser<'a, 'b> { parser: &'b mut Parser<'a>, + is_async: bool, } impl<'a, 'b> TableParser<'a, 'b> { - pub fn new(parser: &'b mut Parser<'a>) -> Self { - Self { parser } + pub fn new(parser: &'b mut Parser<'a>, is_async: bool) -> Self { + Self { parser, is_async } } pub fn run(&mut self) -> Result { let (name, tk) = self.parser.parse_identifier("table name")?; - let mut table = Table::new(name, tk); + let mut table = Table::new(name, self.is_async, tk); self.parse_body(&mut table)?; @@ -1487,7 +1565,12 @@ impl<'a, 'b> StatementParser<'a, 'b> { pub fn parse_call(&mut self, lval: Lvalue) -> Result { let args = self.parser.parse_expr_parameters()?; - Ok(Statement::Call(Call { lval, args })) + let with_await = self.parser.parse_await().is_ok(); + Ok(Statement::Call(Call { + lval, + args, + with_await, + })) } pub fn parse_parameterized_call( @@ -1619,9 +1702,14 @@ impl<'a, 'b> ExpressionParser<'a, 'b> { else if token.kind == lexer::Kind::ParenOpen { self.parser.backlog.push(token.clone()); let args = self.parser.parse_expr_parameters()?; + let with_await = self.parser.parse_await().is_ok(); Expression::new( token, - ExpressionKind::Call(Call { lval, args }), + ExpressionKind::Call(Call { + lval, + args, + with_await, + }), ) } // if it's not an index and it's not a call, it's an lvalue diff --git a/p4/src/util.rs b/p4/src/util.rs index 769e8105..388b05b2 100644 --- a/p4/src/util.rs +++ b/p4/src/util.rs @@ -26,7 +26,8 @@ pub fn resolve_lvalue( Type::List(_) => root.clone(), Type::State => root.clone(), Type::Action => root.clone(), - Type::UserDefined(name) => { + Type::Sync(_) => root.clone(), + Type::UserDefined(name, _) => { if lval.degree() == 1 { root.clone() } else if let Some(parent) = ast.get_struct(name) { diff --git a/x4c/Cargo.toml b/x4c/Cargo.toml index a4360427..deaf024b 100644 --- a/x4c/Cargo.toml +++ b/x4c/Cargo.toml @@ -10,3 +10,4 @@ anyhow.workspace = true regex.workspace = true p4.workspace = true p4-rust.workspace = true +p4-htq.workspace = true diff --git a/x4c/src/bin/x4c.rs b/x4c/src/bin/x4c.rs index b44db245..e107ab30 100644 --- a/x4c/src/bin/x4c.rs +++ b/x4c/src/bin/x4c.rs @@ -31,12 +31,16 @@ fn run() -> Result<()> { p4_rust::emit( &ast, &hlir, - &opts.out, + &format!("{}.rs", opts.out), p4_rust::Settings { pipeline_name: "main".to_owned(), }, )?; } + x4c::Target::Htq => { + let (hlir, _) = p4::check::all(&ast); + p4_htq::emit(&ast, &hlir, &format!("{}.htq", opts.out))?; + } x4c::Target::RedHawk => { todo!("RedHawk code generator"); } diff --git a/x4c/src/lib.rs b/x4c/src/lib.rs index 62457027..ce4212db 100644 --- a/x4c/src/lib.rs +++ b/x4c/src/lib.rs @@ -33,7 +33,7 @@ pub struct Opts { pub filename: String, /// What target to generate code for. - #[clap(value_enum, default_value_t = Target::Rust)] + #[clap(long, value_enum, default_value_t = Target::Rust)] pub target: Target, /// Just check code, do not compile. @@ -41,7 +41,7 @@ pub struct Opts { pub check: bool, /// Filename to write generated code to. - #[clap(short, long, default_value = "out.rs")] + #[clap(short, long, default_value = "out")] pub out: String, } @@ -49,6 +49,7 @@ pub struct Opts { pub enum Target { Rust, RedHawk, + Htq, Docs, }