From 7f6c30b7d432fd07d6f5ccb8c0a47ee9ad0743ca Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Sun, 26 May 2024 11:19:07 +0200 Subject: [PATCH 1/7] Emit EOF header if section directives present in code --- etk-asm/src/asm.rs | 59 +++++++++++++++++++++++++++++++-- etk-asm/src/ops.rs | 6 ++++ etk-asm/src/parse/asm.pest | 8 ++++- etk-asm/src/parse/mod.rs | 4 +++ etk-asm/tests/asm.rs | 11 ++++++ etk-asm/tests/asm/eof/main1.etk | 2 ++ 6 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 etk-asm/tests/asm/eof/main1.etk diff --git a/etk-asm/src/asm.rs b/etk-asm/src/asm.rs index 7add835a..2ad8ab35 100644 --- a/etk-asm/src/asm.rs +++ b/etk-asm/src/asm.rs @@ -219,6 +219,9 @@ pub struct Assembler { /// Pushes that are variable-sized and need to be backpatched. variable_sized_push: Vec, + + /// Positions of sections, if assembling EOF + sections: Vec, } /// A label definition. @@ -343,6 +346,7 @@ impl Assembler { RawOp::Op(AbstractOp::Macro(ref m)) => { self.expand_macro(&m.name, &m.parameters)?; } + RawOp::Op(AbstractOp::EOFSection) => self.sections.push(self.concrete_len), RawOp::Op(ref op) => { match op .clone() @@ -416,7 +420,7 @@ impl Assembler { Ok(self.concrete_len) } - fn backpatch_labels(&mut self) -> Result<(), Error> { + fn backpatch_labels_and_sections(&mut self) -> Result<(), Error> { for pushdef in self.variable_sized_push.iter() { if let AbstractOp::Push(imm) = &pushdef.op { let exp = imm @@ -441,6 +445,15 @@ impl Assembler { updated: true, }); } + for section in self.sections.iter_mut() { + if *section < pushdef.position { + // don't move sections that are declared earlier than this push + continue; + }; + + *section += imm_size as usize - 1; + // TODO check whether `updated` flag is needed + } } } } @@ -473,7 +486,7 @@ impl Assembler { } .fail(); } - self.backpatch_labels()?; + self.backpatch_labels_and_sections()?; let output = match self.emit_bytecode() { Ok(value) => value, Err(value) => return value, @@ -484,6 +497,10 @@ impl Assembler { fn emit_bytecode(&mut self) -> Result, Result, Error>> { let mut output = Vec::new(); + if !self.sections.is_empty() { + self.emit_eof_header(&mut output); + } + for op in self.ready.iter() { let op = match op { RawOp::Op(ref op) => op, @@ -523,6 +540,44 @@ impl Assembler { Ok(output) } + fn emit_eof_header(&self, output: &mut Vec) { + // TODO issue an error if first section doesn't start at 0 + + output.extend_from_slice(&[0xef, 0x00, 0x01]); + // Type section header + output.push(0x01); + let type_section_size = (self.sections.len() * 4) as u16; + output.extend_from_slice(&type_section_size.to_be_bytes()); + // Code section headers + output.push(0x02); + let code_section_num = self.sections.len() as u16; + output.extend_from_slice(&code_section_num.to_be_bytes()); + + // Calculate section sizes + let mut section_sizes = Vec::with_capacity(self.sections.len()); + for section_bounds in self.sections.windows(2) { + if let [start, end] = section_bounds { + section_sizes.push(end - start); + } + } + // add last section + if let Some(&last_section_offset) = self.sections.last() { + section_sizes.push(self.concrete_len - last_section_offset); + } + + for section_size in section_sizes { + let size = section_size as u16; + output.extend_from_slice(&size.to_be_bytes()); + } + // data section header + terminator + output.extend_from_slice(&[0x04, 0x00, 0x00, 0x00]); + // types section + for _ in &self.sections { + // TODO all functions are 0 inputs, non-returning, 0 max stack for now + output.extend_from_slice(&[0x00, 0x80, 0x00, 0x00]); + } + } + fn declare_label(&mut self, rop: &RawOp) -> Result<(), Error> { if let RawOp::Op(AbstractOp::Label(label)) = rop { if self.declared_labels.contains_key(label) { diff --git a/etk-asm/src/ops.rs b/etk-asm/src/ops.rs index 1749459a..b7e86a49 100644 --- a/etk-asm/src/ops.rs +++ b/etk-asm/src/ops.rs @@ -185,6 +185,9 @@ pub enum AbstractOp { /// A user-defined macro, which is a virtual instruction. Macro(InstructionMacroInvocation), + + /// EOF Section + EOFSection, } impl AbstractOp { @@ -232,6 +235,7 @@ impl AbstractOp { Self::Label(_) => panic!("labels cannot be concretized"), Self::Macro(_) => panic!("macros cannot be concretized"), Self::MacroDefinition(_) => panic!("macro definitions cannot be concretized"), + Self::EOFSection => panic!("EOF sections cannot be concretized"), } } @@ -265,6 +269,7 @@ impl AbstractOp { Self::Push(_) => None, Self::Macro(_) => None, Self::MacroDefinition(_) => None, + Self::EOFSection => None, } } @@ -314,6 +319,7 @@ impl fmt::Display for AbstractOp { Self::Label(lbl) => write!(f, r#"{}:"#, lbl), Self::Macro(m) => write!(f, "{}", m), Self::MacroDefinition(defn) => write!(f, "{}", defn), + Self::EOFSection => write!(f, "EOF section"), } } } diff --git a/etk-asm/src/parse/asm.pest b/etk-asm/src/parse/asm.pest index 19c9f1bd..1b9a93bb 100644 --- a/etk-asm/src/parse/asm.pest +++ b/etk-asm/src/parse/asm.pest @@ -3,7 +3,7 @@ /////////////////////// program = _{ SOI ~ inner ~ EOI } inner = _{ NEWLINE* ~ (stmt ~ (NEWLINE+|";"))* ~ stmt? } -stmt = _{ label_definition | builtin | local_macro | push | op } +stmt = _{ label_definition | builtin | local_macro | push | op | section } ////////////////////// // opcode mnemonics // @@ -68,6 +68,12 @@ function_invocation = _{ function_name ~ "(" ~ expression* ~ ("," ~ expression)* function_name = @{ ( ASCII_ALPHA | "_" ) ~ ( ASCII_ALPHANUMERIC | "_" )* } function_parameter = @{ ASCII_ALPHA ~ ASCII_ALPHANUMERIC* } +////////////// +// sections // +////////////// +section = @{ "section" ~ WHITESPACE ~ section_kind } +section_kind = { (".code" | ".data") } + ////////////// // operands // ////////////// diff --git a/etk-asm/src/parse/mod.rs b/etk-asm/src/parse/mod.rs index 4858abaa..5e4c2423 100644 --- a/etk-asm/src/parse/mod.rs +++ b/etk-asm/src/parse/mod.rs @@ -54,6 +54,10 @@ fn parse_abstract_op(pair: Pair) -> Result { let op = Op::new(spec).unwrap(); AbstractOp::Op(op) } + Rule::section => { + // TODO get section kind + AbstractOp::EOFSection + } _ => unreachable!(), }; diff --git a/etk-asm/tests/asm.rs b/etk-asm/tests/asm.rs index 24bf3c81..cb8c2f45 100644 --- a/etk-asm/tests/asm.rs +++ b/etk-asm/tests/asm.rs @@ -348,3 +348,14 @@ fn test_include_hex() -> Result<(), Error> { Ok(()) } + +#[test] +fn test_eof_minimal() -> Result<(), Error> { + let mut output = Vec::new(); + let mut ingester = Ingest::new(&mut output); + ingester.ingest_file(source(&["eof", "main1.etk"]))?; + + assert_eq!(output, hex!("ef00010100040200010001040000000080000000")); + + Ok(()) +} diff --git a/etk-asm/tests/asm/eof/main1.etk b/etk-asm/tests/asm/eof/main1.etk new file mode 100644 index 00000000..07e5ae92 --- /dev/null +++ b/etk-asm/tests/asm/eof/main1.etk @@ -0,0 +1,2 @@ +section .code +stop \ No newline at end of file From 9bbe001866385c92031406026ae8752d75efd748 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Sun, 26 May 2024 11:19:28 +0200 Subject: [PATCH 2/7] Test with multiple code sections --- etk-asm/tests/asm.rs | 19 ++++++++++++++++++- etk-asm/tests/asm/eof/main2.etk | 10 ++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 etk-asm/tests/asm/eof/main2.etk diff --git a/etk-asm/tests/asm.rs b/etk-asm/tests/asm.rs index cb8c2f45..9ba04dea 100644 --- a/etk-asm/tests/asm.rs +++ b/etk-asm/tests/asm.rs @@ -355,7 +355,24 @@ fn test_eof_minimal() -> Result<(), Error> { let mut ingester = Ingest::new(&mut output); ingester.ingest_file(source(&["eof", "main1.etk"]))?; - assert_eq!(output, hex!("ef00010100040200010001040000000080000000")); + assert_eq!( + output, + hex!("ef0001 010004 0200010001 040000 00 00800000 00") + ); + + Ok(()) +} + +#[test] +fn test_eof_multiple_code_sections() -> Result<(), Error> { + let mut output = Vec::new(); + let mut ingester = Ingest::new(&mut output); + ingester.ingest_file(source(&["eof", "main2.etk"]))?; + + assert_eq!( + output, + hex!("ef0001 01000c 020003000100010003 040000 00 00800000 00800000 00800000 00 fe 5f5ff3") + ); Ok(()) } diff --git a/etk-asm/tests/asm/eof/main2.etk b/etk-asm/tests/asm/eof/main2.etk new file mode 100644 index 00000000..895d653e --- /dev/null +++ b/etk-asm/tests/asm/eof/main2.etk @@ -0,0 +1,10 @@ +section .code +stop + +section .code +invalid + +section .code +push0 +push0 +return \ No newline at end of file From cc8ac1b97f341d5a69e8ae385e89295dc4358a3a Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Fri, 21 Jun 2024 16:03:29 +0200 Subject: [PATCH 3/7] Data section support --- etk-asm/src/asm.rs | 67 ++++++++++++++++++++++----------- etk-asm/src/ops.rs | 31 +++++++++++++-- etk-asm/src/parse/asm.pest | 4 +- etk-asm/src/parse/mod.rs | 12 ++++-- etk-asm/tests/asm.rs | 11 ++++++ etk-asm/tests/asm/eof/data.etk | 1 + etk-asm/tests/asm/eof/main3.etk | 13 +++++++ 7 files changed, 108 insertions(+), 31 deletions(-) create mode 100644 etk-asm/tests/asm/eof/data.etk create mode 100644 etk-asm/tests/asm/eof/main3.etk diff --git a/etk-asm/src/asm.rs b/etk-asm/src/asm.rs index 2ad8ab35..2958dc3c 100644 --- a/etk-asm/src/asm.rs +++ b/etk-asm/src/asm.rs @@ -142,7 +142,7 @@ mod error { pub use self::error::Error; use crate::ops::expression::Error::{UndefinedVariable, UnknownLabel, UnknownMacro}; -use crate::ops::{self, AbstractOp, Assemble, Expression, MacroDefinition}; +use crate::ops::{self, AbstractOp, Assemble, EOFSectionKind, Expression, MacroDefinition}; use indexmap::IndexMap; use num_bigint::BigInt; use rand::Rng; @@ -221,7 +221,7 @@ pub struct Assembler { variable_sized_push: Vec, /// Positions of sections, if assembling EOF - sections: Vec, + sections: Vec, } /// A label definition. @@ -270,6 +270,13 @@ impl PushDef { } } +/// An EOF section definition. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct SectionDef { + position: usize, + kind: EOFSectionKind, +} + impl Assembler { /// Create a new `Assembler`. pub fn new() -> Self { @@ -346,7 +353,10 @@ impl Assembler { RawOp::Op(AbstractOp::Macro(ref m)) => { self.expand_macro(&m.name, &m.parameters)?; } - RawOp::Op(AbstractOp::EOFSection) => self.sections.push(self.concrete_len), + RawOp::Op(AbstractOp::EOFSection(kind)) => self.sections.push(SectionDef { + position: self.concrete_len, + kind, + }), RawOp::Op(ref op) => { match op .clone() @@ -446,12 +456,12 @@ impl Assembler { }); } for section in self.sections.iter_mut() { - if *section < pushdef.position { + if section.position < pushdef.position { // don't move sections that are declared earlier than this push continue; }; - *section += imm_size as usize - 1; + section.position += imm_size as usize - 1; // TODO check whether `updated` flag is needed } } @@ -543,36 +553,49 @@ impl Assembler { fn emit_eof_header(&self, output: &mut Vec) { // TODO issue an error if first section doesn't start at 0 - output.extend_from_slice(&[0xef, 0x00, 0x01]); - // Type section header - output.push(0x01); - let type_section_size = (self.sections.len() * 4) as u16; - output.extend_from_slice(&type_section_size.to_be_bytes()); - // Code section headers - output.push(0x02); - let code_section_num = self.sections.len() as u16; - output.extend_from_slice(&code_section_num.to_be_bytes()); + // TODO error if data section is not last // Calculate section sizes - let mut section_sizes = Vec::with_capacity(self.sections.len()); + let mut code_section_sizes = Vec::with_capacity(self.sections.len()); + let mut data_section_size = 0; for section_bounds in self.sections.windows(2) { if let [start, end] = section_bounds { - section_sizes.push(end - start); + code_section_sizes.push(end.position - start.position); } } + // add last section - if let Some(&last_section_offset) = self.sections.last() { - section_sizes.push(self.concrete_len - last_section_offset); + if let Some(&last_section) = self.sections.last() { + let last_section_size = self.concrete_len - last_section.position; + if last_section.kind == EOFSectionKind::Code { + code_section_sizes.push(last_section_size); + } else { + data_section_size = last_section_size + } } - for section_size in section_sizes { - let size = section_size as u16; + output.extend_from_slice(&[0xef, 0x00, 0x01]); + // Type section header + output.push(0x01); + let type_section_size = (code_section_sizes.len() * 4) as u16; + output.extend_from_slice(&type_section_size.to_be_bytes()); + // Code section headers + output.push(0x02); + + let code_section_num = code_section_sizes.len() as u16; + output.extend_from_slice(&code_section_num.to_be_bytes()); + + for code_section_size in &code_section_sizes { + let size = *code_section_size as u16; output.extend_from_slice(&size.to_be_bytes()); } // data section header + terminator - output.extend_from_slice(&[0x04, 0x00, 0x00, 0x00]); + output.push(0x04); + output.extend_from_slice(&data_section_size.to_be_bytes()); + // terminator + output.push(0x00); // types section - for _ in &self.sections { + for _ in code_section_sizes { // TODO all functions are 0 inputs, non-returning, 0 max stack for now output.extend_from_slice(&[0x00, 0x80, 0x00, 0x00]); } diff --git a/etk-asm/src/ops.rs b/etk-asm/src/ops.rs index b7e86a49..c35e4422 100644 --- a/etk-asm/src/ops.rs +++ b/etk-asm/src/ops.rs @@ -165,6 +165,15 @@ impl Access { } } +/// Kind of EOF section +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum EOFSectionKind { + /// Code section + Code, + /// Data section + Data, +} + /// Like an [`Op`], except it also supports virtual instructions. /// /// In addition to the real EVM instructions, `AbstractOp` also supports defining @@ -187,7 +196,7 @@ pub enum AbstractOp { Macro(InstructionMacroInvocation), /// EOF Section - EOFSection, + EOFSection(EOFSectionKind), } impl AbstractOp { @@ -235,7 +244,7 @@ impl AbstractOp { Self::Label(_) => panic!("labels cannot be concretized"), Self::Macro(_) => panic!("macros cannot be concretized"), Self::MacroDefinition(_) => panic!("macro definitions cannot be concretized"), - Self::EOFSection => panic!("EOF sections cannot be concretized"), + Self::EOFSection(_) => panic!("EOF sections cannot be concretized"), } } @@ -269,7 +278,7 @@ impl AbstractOp { Self::Push(_) => None, Self::Macro(_) => None, Self::MacroDefinition(_) => None, - Self::EOFSection => None, + Self::EOFSection(_) => None, } } @@ -305,6 +314,20 @@ impl From for AbstractOp { } } +impl fmt::Display for EOFSectionKind { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Code => { + write!(f, "code")?; + } + Self::Data => { + write!(f, "data")?; + } + } + Ok(()) + } +} + impl fmt::Display for AbstractOp { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { @@ -319,7 +342,7 @@ impl fmt::Display for AbstractOp { Self::Label(lbl) => write!(f, r#"{}:"#, lbl), Self::Macro(m) => write!(f, "{}", m), Self::MacroDefinition(defn) => write!(f, "{}", defn), - Self::EOFSection => write!(f, "EOF section"), + Self::EOFSection(kind) => write!(f, "EOF {} section", kind), } } } diff --git a/etk-asm/src/parse/asm.pest b/etk-asm/src/parse/asm.pest index 1b9a93bb..c3d2746b 100644 --- a/etk-asm/src/parse/asm.pest +++ b/etk-asm/src/parse/asm.pest @@ -71,8 +71,8 @@ function_parameter = @{ ASCII_ALPHA ~ ASCII_ALPHANUMERIC* } ////////////// // sections // ////////////// -section = @{ "section" ~ WHITESPACE ~ section_kind } -section_kind = { (".code" | ".data") } +section = ${ "section" ~ WHITESPACE ~ section_kind } +section_kind = { ".code" | ".data" } ////////////// // operands // diff --git a/etk-asm/src/parse/mod.rs b/etk-asm/src/parse/mod.rs index 5e4c2423..f95a5116 100644 --- a/etk-asm/src/parse/mod.rs +++ b/etk-asm/src/parse/mod.rs @@ -21,7 +21,7 @@ use self::{ }; use crate::ast::Node; -use crate::ops::AbstractOp; +use crate::ops::{AbstractOp, EOFSectionKind}; use etk_ops::cancun::Op; use num_bigint::BigInt; use pest::{iterators::Pair, Parser}; @@ -55,8 +55,14 @@ fn parse_abstract_op(pair: Pair) -> Result { AbstractOp::Op(op) } Rule::section => { - // TODO get section kind - AbstractOp::EOFSection + let mut pair = pair.into_inner(); + let section_kind = pair.next().unwrap().as_str(); + + if section_kind == ".code" { + AbstractOp::EOFSection(EOFSectionKind::Code) + } else { + AbstractOp::EOFSection(EOFSectionKind::Data) + } } _ => unreachable!(), }; diff --git a/etk-asm/tests/asm.rs b/etk-asm/tests/asm.rs index 9ba04dea..7d4aad6e 100644 --- a/etk-asm/tests/asm.rs +++ b/etk-asm/tests/asm.rs @@ -376,3 +376,14 @@ fn test_eof_multiple_code_sections() -> Result<(), Error> { Ok(()) } + +#[test] +fn test_eof_data_section() -> Result<(), Error> { + let mut output = Vec::new(); + let mut ingester = Ingest::new(&mut output); + ingester.ingest_file(source(&["eof", "main3.etk"]))?; + + assert_eq!(output, hex!("ef0001 01000c 020003000100010003 040004 00 00800000 00800000 00800000 00 fe 5f5ff3 cafeb0ba")); + + Ok(()) +} diff --git a/etk-asm/tests/asm/eof/data.etk b/etk-asm/tests/asm/eof/data.etk new file mode 100644 index 00000000..f4c07032 --- /dev/null +++ b/etk-asm/tests/asm/eof/data.etk @@ -0,0 +1 @@ +cafeb0ba \ No newline at end of file diff --git a/etk-asm/tests/asm/eof/main3.etk b/etk-asm/tests/asm/eof/main3.etk new file mode 100644 index 00000000..d7eea119 --- /dev/null +++ b/etk-asm/tests/asm/eof/main3.etk @@ -0,0 +1,13 @@ +section .code +stop + +section .code +invalid + +section .code +push0 +push0 +return + +section .data +%include_hex("data.etk") \ No newline at end of file From 4e27657dbc96f08284b274649d6b7fe0c40261de Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Sun, 26 May 2024 11:20:26 +0200 Subject: [PATCH 4/7] Require EOF code to start with section declaration --- etk-asm/src/asm.rs | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/etk-asm/src/asm.rs b/etk-asm/src/asm.rs index 2958dc3c..976296ec 100644 --- a/etk-asm/src/asm.rs +++ b/etk-asm/src/asm.rs @@ -137,6 +137,11 @@ mod error { /// The location of the error. backtrace: Backtrace, }, + + /// Code containing secions does not start with section declaration. + #[snafu(display("EOF code does not start with section declaration"))] + #[non_exhaustive] + EOFCodeDoesNotStartWithSection, } } @@ -508,7 +513,9 @@ impl Assembler { fn emit_bytecode(&mut self) -> Result, Result, Error>> { let mut output = Vec::new(); if !self.sections.is_empty() { - self.emit_eof_header(&mut output); + if let Err(err) = self.emit_eof_header(&mut output) { + return Err(Err(err)); // Convert the error to the nested `Result` type + } } for op in self.ready.iter() { @@ -550,8 +557,11 @@ impl Assembler { Ok(output) } - fn emit_eof_header(&self, output: &mut Vec) { - // TODO issue an error if first section doesn't start at 0 + fn emit_eof_header(&self, output: &mut Vec) -> Result<(), Error> { + // Error if some code preceeds 0th section declaration + if self.sections.first().unwrap().position != 0 { + return error::EOFCodeDoesNotStartWithSection.fail(); + } // TODO error if data section is not last @@ -599,6 +609,7 @@ impl Assembler { // TODO all functions are 0 inputs, non-returning, 0 max stack for now output.extend_from_slice(&[0x00, 0x80, 0x00, 0x00]); } + Ok(()) } fn declare_label(&mut self, rop: &RawOp) -> Result<(), Error> { @@ -1615,4 +1626,20 @@ mod tests { Ok(()) } + + #[test] + fn assemble_eof_not_starting_with_section() { + let mut asm = Assembler::new(); + + let code = vec![ + AbstractOp::new(Push0), + AbstractOp::new(Stop), + AbstractOp::EOFSection(EOFSectionKind::Code), + AbstractOp::new(Stop), + ]; + + let err = asm.assemble(&code).unwrap_err(); + + assert_matches!(err, Error::EOFCodeDoesNotStartWithSection {}); + } } From bfadee68e9650c0bdb7062d7d1f8b4d34a2edc88 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Sun, 26 May 2024 11:20:56 +0200 Subject: [PATCH 5/7] Require data section to be the last section --- etk-asm/src/asm.rs | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/etk-asm/src/asm.rs b/etk-asm/src/asm.rs index 976296ec..f792752a 100644 --- a/etk-asm/src/asm.rs +++ b/etk-asm/src/asm.rs @@ -142,6 +142,11 @@ mod error { #[snafu(display("EOF code does not start with section declaration"))] #[non_exhaustive] EOFCodeDoesNotStartWithSection, + + /// Code containing secions does not start with section declaration. + #[snafu(display("EOF data section is not the last section"))] + #[non_exhaustive] + EOFDataSectionIsNotTheLast, } } @@ -563,7 +568,16 @@ impl Assembler { return error::EOFCodeDoesNotStartWithSection.fail(); } - // TODO error if data section is not last + // Error if data section is not the last + if let Some(index) = self + .sections + .iter() + .position(|§ion| section.kind == EOFSectionKind::Data) + { + if index != self.sections.len() - 1 { + return error::EOFDataSectionIsNotTheLast.fail(); + } + } // Calculate section sizes let mut code_section_sizes = Vec::with_capacity(self.sections.len()); @@ -1642,4 +1656,23 @@ mod tests { assert_matches!(err, Error::EOFCodeDoesNotStartWithSection {}); } + + #[test] + fn assemble_eof_data_section_not_the_last() { + let mut asm = Assembler::new(); + + let code = vec![ + AbstractOp::EOFSection(EOFSectionKind::Code), + AbstractOp::new(Push0), + AbstractOp::new(Stop), + AbstractOp::EOFSection(EOFSectionKind::Data), + AbstractOp::new(JumpDest), + AbstractOp::EOFSection(EOFSectionKind::Code), + AbstractOp::new(Stop), + ]; + + let err = asm.assemble(&code).unwrap_err(); + + assert_matches!(err, Error::EOFDataSectionIsNotTheLast {}); + } } From 8d2be900540b55136fdd8dcead34838300167a98 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Sun, 26 May 2024 11:21:38 +0200 Subject: [PATCH 6/7] Support specifying max_stack_height for code sections --- etk-asm/src/asm.rs | 57 +++++++++++++++++++++++---------- etk-asm/src/ops.rs | 7 ++-- etk-asm/src/parse/asm.pest | 4 ++- etk-asm/src/parse/mod.rs | 24 ++++++++++++-- etk-asm/tests/asm.rs | 4 +-- etk-asm/tests/asm/eof/main2.etk | 2 +- etk-asm/tests/asm/eof/main3.etk | 2 +- 7 files changed, 73 insertions(+), 27 deletions(-) diff --git a/etk-asm/src/asm.rs b/etk-asm/src/asm.rs index f792752a..61290447 100644 --- a/etk-asm/src/asm.rs +++ b/etk-asm/src/asm.rs @@ -579,39 +579,55 @@ impl Assembler { } } + #[derive(Clone, Copy, Debug, PartialEq)] + struct EOFCodeSection { + size: u16, + max_stack_height: u16, + } + // Calculate section sizes - let mut code_section_sizes = Vec::with_capacity(self.sections.len()); + let mut code_sections = Vec::with_capacity(self.sections.len()); let mut data_section_size = 0; for section_bounds in self.sections.windows(2) { if let [start, end] = section_bounds { - code_section_sizes.push(end.position - start.position); + let size = (end.position - start.position) as u16; + if let EOFSectionKind::Code { max_stack_height } = start.kind { + code_sections.push(EOFCodeSection { + size, + max_stack_height, + }); + } else { + unreachable!("data section was checked to be the last one") + } } } // add last section if let Some(&last_section) = self.sections.last() { - let last_section_size = self.concrete_len - last_section.position; - if last_section.kind == EOFSectionKind::Code { - code_section_sizes.push(last_section_size); + let size = (self.concrete_len - last_section.position) as u16; + if let EOFSectionKind::Code { max_stack_height } = last_section.kind { + code_sections.push(EOFCodeSection { + size, + max_stack_height, + }); } else { - data_section_size = last_section_size + data_section_size = size } } output.extend_from_slice(&[0xef, 0x00, 0x01]); // Type section header output.push(0x01); - let type_section_size = (code_section_sizes.len() * 4) as u16; + let type_section_size = (code_sections.len() * 4) as u16; output.extend_from_slice(&type_section_size.to_be_bytes()); // Code section headers output.push(0x02); - let code_section_num = code_section_sizes.len() as u16; + let code_section_num = code_sections.len() as u16; output.extend_from_slice(&code_section_num.to_be_bytes()); - for code_section_size in &code_section_sizes { - let size = *code_section_size as u16; - output.extend_from_slice(&size.to_be_bytes()); + for code_section_size in &code_sections { + output.extend_from_slice(&code_section_size.size.to_be_bytes()); } // data section header + terminator output.push(0x04); @@ -619,9 +635,10 @@ impl Assembler { // terminator output.push(0x00); // types section - for _ in code_section_sizes { - // TODO all functions are 0 inputs, non-returning, 0 max stack for now - output.extend_from_slice(&[0x00, 0x80, 0x00, 0x00]); + for code_section in code_sections { + // TODO all functions are 0 inputs, non-returning for now + output.extend_from_slice(&[0x00, 0x80]); + output.extend_from_slice(&code_section.max_stack_height.to_be_bytes()); } Ok(()) } @@ -1648,7 +1665,9 @@ mod tests { let code = vec![ AbstractOp::new(Push0), AbstractOp::new(Stop), - AbstractOp::EOFSection(EOFSectionKind::Code), + AbstractOp::EOFSection(EOFSectionKind::Code { + max_stack_height: 0, + }), AbstractOp::new(Stop), ]; @@ -1662,12 +1681,16 @@ mod tests { let mut asm = Assembler::new(); let code = vec![ - AbstractOp::EOFSection(EOFSectionKind::Code), + AbstractOp::EOFSection(EOFSectionKind::Code { + max_stack_height: 1, + }), AbstractOp::new(Push0), AbstractOp::new(Stop), AbstractOp::EOFSection(EOFSectionKind::Data), AbstractOp::new(JumpDest), - AbstractOp::EOFSection(EOFSectionKind::Code), + AbstractOp::EOFSection(EOFSectionKind::Code { + max_stack_height: 0, + }), AbstractOp::new(Stop), ]; diff --git a/etk-asm/src/ops.rs b/etk-asm/src/ops.rs index c35e4422..ad551385 100644 --- a/etk-asm/src/ops.rs +++ b/etk-asm/src/ops.rs @@ -169,7 +169,10 @@ impl Access { #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum EOFSectionKind { /// Code section - Code, + Code { + /// Code section's max stack height + max_stack_height: u16, + }, /// Data section Data, } @@ -317,7 +320,7 @@ impl From for AbstractOp { impl fmt::Display for EOFSectionKind { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Self::Code => { + Self::Code { .. } => { write!(f, "code")?; } Self::Data => { diff --git a/etk-asm/src/parse/asm.pest b/etk-asm/src/parse/asm.pest index c3d2746b..cccb771c 100644 --- a/etk-asm/src/parse/asm.pest +++ b/etk-asm/src/parse/asm.pest @@ -71,8 +71,10 @@ function_parameter = @{ ASCII_ALPHA ~ ASCII_ALPHANUMERIC* } ////////////// // sections // ////////////// -section = ${ "section" ~ WHITESPACE ~ section_kind } +section = ${ "section" ~ WHITESPACE ~ section_kind ~ (WHITESPACE ~ section_attributes)? } section_kind = { ".code" | ".data" } +section_attributes = { max_stack_height_attribute? } +max_stack_height_attribute = { "max_stack_height" ~ "=" ~ number } ////////////// // operands // diff --git a/etk-asm/src/parse/mod.rs b/etk-asm/src/parse/mod.rs index f95a5116..514c80c9 100644 --- a/etk-asm/src/parse/mod.rs +++ b/etk-asm/src/parse/mod.rs @@ -55,12 +55,30 @@ fn parse_abstract_op(pair: Pair) -> Result { AbstractOp::Op(op) } Rule::section => { - let mut pair = pair.into_inner(); - let section_kind = pair.next().unwrap().as_str(); + let mut pairs = pair.into_inner(); + let section_kind = pairs.next().unwrap().as_str(); if section_kind == ".code" { - AbstractOp::EOFSection(EOFSectionKind::Code) + let max_stack_height = { + if let Some(section_attributes) = pairs.next() { + if let Some(msh_pair) = section_attributes.into_inner().next() { + msh_pair + .into_inner() + .next() + .unwrap() + .as_str() + .parse::() + .unwrap() + } else { + 0 + } + } else { + 0 + } + }; + AbstractOp::EOFSection(EOFSectionKind::Code { max_stack_height }) } else { + // attributes are ignored AbstractOp::EOFSection(EOFSectionKind::Data) } } diff --git a/etk-asm/tests/asm.rs b/etk-asm/tests/asm.rs index 7d4aad6e..4f6f5a82 100644 --- a/etk-asm/tests/asm.rs +++ b/etk-asm/tests/asm.rs @@ -371,7 +371,7 @@ fn test_eof_multiple_code_sections() -> Result<(), Error> { assert_eq!( output, - hex!("ef0001 01000c 020003000100010003 040000 00 00800000 00800000 00800000 00 fe 5f5ff3") + hex!("ef0001 01000c 020003000100010003 040000 00 00800000 00800000 00800002 00 fe 5f5ff3") ); Ok(()) @@ -383,7 +383,7 @@ fn test_eof_data_section() -> Result<(), Error> { let mut ingester = Ingest::new(&mut output); ingester.ingest_file(source(&["eof", "main3.etk"]))?; - assert_eq!(output, hex!("ef0001 01000c 020003000100010003 040004 00 00800000 00800000 00800000 00 fe 5f5ff3 cafeb0ba")); + assert_eq!(output, hex!("ef0001 01000c 020003000100010003 040004 00 00800000 00800000 00800002 00 fe 5f5ff3 cafeb0ba")); Ok(()) } diff --git a/etk-asm/tests/asm/eof/main2.etk b/etk-asm/tests/asm/eof/main2.etk index 895d653e..ebd0febf 100644 --- a/etk-asm/tests/asm/eof/main2.etk +++ b/etk-asm/tests/asm/eof/main2.etk @@ -4,7 +4,7 @@ stop section .code invalid -section .code +section .code max_stack_height=2 push0 push0 return \ No newline at end of file diff --git a/etk-asm/tests/asm/eof/main3.etk b/etk-asm/tests/asm/eof/main3.etk index d7eea119..51ce7337 100644 --- a/etk-asm/tests/asm/eof/main3.etk +++ b/etk-asm/tests/asm/eof/main3.etk @@ -4,7 +4,7 @@ stop section .code invalid -section .code +section .code max_stack_height=2 push0 push0 return From 106a60c708278d1c7ca6d5a9f44ad12f8bee7343 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Sun, 26 May 2024 11:22:30 +0200 Subject: [PATCH 7/7] Support specifying inputs and outputs for code sections --- etk-asm/src/asm.rs | 30 ++++++++++++-- etk-asm/src/ops.rs | 4 ++ etk-asm/src/parse/asm.pest | 6 ++- etk-asm/src/parse/mod.rs | 72 ++++++++++++++++++++------------- etk-asm/tests/asm.rs | 5 +-- etk-asm/tests/asm/eof/main2.etk | 12 ++++-- 6 files changed, 89 insertions(+), 40 deletions(-) diff --git a/etk-asm/src/asm.rs b/etk-asm/src/asm.rs index 61290447..372ac9f8 100644 --- a/etk-asm/src/asm.rs +++ b/etk-asm/src/asm.rs @@ -582,6 +582,8 @@ impl Assembler { #[derive(Clone, Copy, Debug, PartialEq)] struct EOFCodeSection { size: u16, + inputs: u8, + outputs: u8, max_stack_height: u16, } @@ -591,9 +593,16 @@ impl Assembler { for section_bounds in self.sections.windows(2) { if let [start, end] = section_bounds { let size = (end.position - start.position) as u16; - if let EOFSectionKind::Code { max_stack_height } = start.kind { + if let EOFSectionKind::Code { + inputs, + outputs, + max_stack_height, + } = start.kind + { code_sections.push(EOFCodeSection { size, + inputs, + outputs, max_stack_height, }); } else { @@ -605,9 +614,16 @@ impl Assembler { // add last section if let Some(&last_section) = self.sections.last() { let size = (self.concrete_len - last_section.position) as u16; - if let EOFSectionKind::Code { max_stack_height } = last_section.kind { + if let EOFSectionKind::Code { + inputs, + outputs, + max_stack_height, + } = last_section.kind + { code_sections.push(EOFCodeSection { size, + inputs, + outputs, max_stack_height, }); } else { @@ -636,8 +652,8 @@ impl Assembler { output.push(0x00); // types section for code_section in code_sections { - // TODO all functions are 0 inputs, non-returning for now - output.extend_from_slice(&[0x00, 0x80]); + output.push(code_section.inputs); + output.push(code_section.outputs); output.extend_from_slice(&code_section.max_stack_height.to_be_bytes()); } Ok(()) @@ -1666,6 +1682,8 @@ mod tests { AbstractOp::new(Push0), AbstractOp::new(Stop), AbstractOp::EOFSection(EOFSectionKind::Code { + inputs: 0, + outputs: 0, max_stack_height: 0, }), AbstractOp::new(Stop), @@ -1682,6 +1700,8 @@ mod tests { let code = vec![ AbstractOp::EOFSection(EOFSectionKind::Code { + inputs: 0, + outputs: 0x80, max_stack_height: 1, }), AbstractOp::new(Push0), @@ -1689,6 +1709,8 @@ mod tests { AbstractOp::EOFSection(EOFSectionKind::Data), AbstractOp::new(JumpDest), AbstractOp::EOFSection(EOFSectionKind::Code { + inputs: 0, + outputs: 0, max_stack_height: 0, }), AbstractOp::new(Stop), diff --git a/etk-asm/src/ops.rs b/etk-asm/src/ops.rs index ad551385..3b8338b4 100644 --- a/etk-asm/src/ops.rs +++ b/etk-asm/src/ops.rs @@ -170,6 +170,10 @@ impl Access { pub enum EOFSectionKind { /// Code section Code { + /// Code section's inputs + inputs: u8, + /// Code section's outputs or 0x80 if secton is non-returning + outputs: u8, /// Code section's max stack height max_stack_height: u16, }, diff --git a/etk-asm/src/parse/asm.pest b/etk-asm/src/parse/asm.pest index cccb771c..3a4197fb 100644 --- a/etk-asm/src/parse/asm.pest +++ b/etk-asm/src/parse/asm.pest @@ -73,8 +73,12 @@ function_parameter = @{ ASCII_ALPHA ~ ASCII_ALPHANUMERIC* } ////////////// section = ${ "section" ~ WHITESPACE ~ section_kind ~ (WHITESPACE ~ section_attributes)? } section_kind = { ".code" | ".data" } -section_attributes = { max_stack_height_attribute? } +section_attributes = { (section_attribute ~WHITESPACE?)* } +section_attribute = { inputs_attribute | outputs_attribute | max_stack_height_attribute } max_stack_height_attribute = { "max_stack_height" ~ "=" ~ number } +inputs_attribute = { "inputs" ~ "=" ~ number } +outputs_attribute = { "outputs" ~ "=" ~ (number | nonreturning) } +nonreturning = { "nonret" } ////////////// // operands // diff --git a/etk-asm/src/parse/mod.rs b/etk-asm/src/parse/mod.rs index 514c80c9..5180dc00 100644 --- a/etk-asm/src/parse/mod.rs +++ b/etk-asm/src/parse/mod.rs @@ -3,6 +3,7 @@ mod expression; mod macros; pub(crate) mod error; + mod parser { #![allow(clippy::upper_case_acronyms)] @@ -54,34 +55,7 @@ fn parse_abstract_op(pair: Pair) -> Result { let op = Op::new(spec).unwrap(); AbstractOp::Op(op) } - Rule::section => { - let mut pairs = pair.into_inner(); - let section_kind = pairs.next().unwrap().as_str(); - - if section_kind == ".code" { - let max_stack_height = { - if let Some(section_attributes) = pairs.next() { - if let Some(msh_pair) = section_attributes.into_inner().next() { - msh_pair - .into_inner() - .next() - .unwrap() - .as_str() - .parse::() - .unwrap() - } else { - 0 - } - } else { - 0 - } - }; - AbstractOp::EOFSection(EOFSectionKind::Code { max_stack_height }) - } else { - // attributes are ignored - AbstractOp::EOFSection(EOFSectionKind::Data) - } - } + Rule::section => parse_section(pair)?, _ => unreachable!(), }; @@ -107,6 +81,48 @@ fn parse_push(pair: Pair) -> Result { Ok(AbstractOp::Op(spec.with(expr).unwrap())) } +fn parse_section(pair: Pair) -> Result { + let mut pairs = pair.into_inner(); + let section_kind = pairs.next().unwrap().as_str(); + + if section_kind == ".code" { + let mut inputs: u8 = 0; + let mut outputs: u8 = 0x80; // non-returning by default + let mut max_stack_height: u16 = 0; + if let Some(section_attributes) = pairs.next() { + for attribute in section_attributes.into_inner() { + let attr_inner = attribute.into_inner().next().unwrap(); + + match attr_inner.as_rule() { + Rule::max_stack_height_attribute => { + let max_stack_height_str = attr_inner.into_inner().next().unwrap().as_str(); + max_stack_height = max_stack_height_str.parse::().unwrap(); + } + Rule::inputs_attribute => { + let inputs_str = attr_inner.into_inner().next().unwrap().as_str(); + inputs = inputs_str.parse::().unwrap(); + } + Rule::outputs_attribute => { + let outputs_str = attr_inner.into_inner().next().unwrap().as_str(); + if outputs_str != "nonret" { + outputs = outputs_str.parse::().unwrap(); + } + } + _ => unreachable!(), + } + } + } + Ok(AbstractOp::EOFSection(EOFSectionKind::Code { + inputs, + outputs, + max_stack_height, + })) + } else { + // attributes are ignored + Ok(AbstractOp::EOFSection(EOFSectionKind::Data)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/etk-asm/tests/asm.rs b/etk-asm/tests/asm.rs index 4f6f5a82..1c87566e 100644 --- a/etk-asm/tests/asm.rs +++ b/etk-asm/tests/asm.rs @@ -369,10 +369,7 @@ fn test_eof_multiple_code_sections() -> Result<(), Error> { let mut ingester = Ingest::new(&mut output); ingester.ingest_file(source(&["eof", "main2.etk"]))?; - assert_eq!( - output, - hex!("ef0001 01000c 020003000100010003 040000 00 00800000 00800000 00800002 00 fe 5f5ff3") - ); + assert_eq!(output, hex!("ef0001 010010 0200040001000100030004 040000 00 00800000 00800000 00800002 01000002 00 fe 5f5ff3 505f5f00")); Ok(()) } diff --git a/etk-asm/tests/asm/eof/main2.etk b/etk-asm/tests/asm/eof/main2.etk index ebd0febf..48a5e4c7 100644 --- a/etk-asm/tests/asm/eof/main2.etk +++ b/etk-asm/tests/asm/eof/main2.etk @@ -1,10 +1,16 @@ section .code stop -section .code +section .code outputs=nonret invalid -section .code max_stack_height=2 +section .code outputs=nonret max_stack_height=2 +push0 +push0 +return + +section .code inputs=1 outputs=0 max_stack_height=2 +pop push0 push0 -return \ No newline at end of file +stop \ No newline at end of file