Skip to content

Commit a69a4df

Browse files
Support the Match expression (#368)
1 parent 08a8333 commit a69a4df

6 files changed

Lines changed: 304 additions & 14 deletions

File tree

‎src/aml/match_expr.rs‎

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use crate::aml::object::Package;
2+
3+
const ONES: u64 = !0;
4+
5+
pub fn do_match(
6+
search_package: &Package,
7+
op_a: &MatchOp,
8+
operand_a: u64,
9+
op_b: &MatchOp,
10+
operand_b: u64,
11+
start_index: u64,
12+
) -> u64 {
13+
for (idx, row) in search_package[start_index as usize..].iter().enumerate() {
14+
let Ok(v) = row.as_integer() else {
15+
continue;
16+
};
17+
18+
if match_comparison(v, op_a, operand_a) && match_comparison(v, op_b, operand_b) {
19+
return start_index + idx as u64;
20+
}
21+
}
22+
23+
ONES
24+
}
25+
26+
fn match_comparison(value: u64, op: &MatchOp, operand: u64) -> bool {
27+
match op {
28+
MatchOp::MTR => true,
29+
MatchOp::MEQ => value == operand,
30+
MatchOp::MLE => value <= operand,
31+
MatchOp::MLT => value < operand,
32+
MatchOp::MGE => value >= operand,
33+
MatchOp::MGT => value > operand,
34+
}
35+
}
36+
37+
#[allow(clippy::upper_case_acronyms)] // Allow the same capitalisation as the spec.
38+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39+
#[repr(u16)]
40+
pub enum MatchOp {
41+
MTR = 0,
42+
MEQ = 1,
43+
MLE = 2,
44+
MLT = 3,
45+
MGE = 4,
46+
MGT = 5,
47+
}
48+
49+
#[cfg(test)]
50+
mod tests {
51+
use super::*;
52+
use crate::aml::object::Object;
53+
use std::string::ToString;
54+
55+
#[test]
56+
fn empty_package_doesnt_match() {
57+
assert_eq!(ONES, do_match(&vec![], &MatchOp::MEQ, 0, &MatchOp::MTR, 0, 0));
58+
}
59+
60+
#[test]
61+
fn basic_match() {
62+
let pkg = &([0, 1, 2, 3].iter().map(|i| Object::Integer(*i as u64).wrap()).collect());
63+
assert_eq!(2, do_match(pkg, &MatchOp::MEQ, 2, &MatchOp::MTR, 0, 0));
64+
}
65+
66+
#[test]
67+
fn skips_strings() {
68+
let pkg = [
69+
Object::Integer(0),
70+
Object::String("1".to_string()),
71+
Object::String("2".to_string()),
72+
Object::Integer(3),
73+
]
74+
.into_iter()
75+
.map(|o| o.wrap())
76+
.collect();
77+
78+
assert_eq!(3, do_match(&pkg, &MatchOp::MGT, 2, &MatchOp::MTR, 0, 0));
79+
}
80+
81+
#[test]
82+
fn skips_packages() {
83+
let pkg = [Object::Integer(0), Object::Package(vec![Object::Integer(1).wrap()]), Object::Integer(2)]
84+
.into_iter()
85+
.map(|o| o.wrap())
86+
.collect();
87+
88+
assert_eq!(2, do_match(&pkg, &MatchOp::MGE, 2, &MatchOp::MTR, 0, 0));
89+
}
90+
91+
#[test]
92+
fn second_op_also_works() {
93+
let pkg = &([0, 1, 2, 3].iter().map(|i| Object::Integer(*i as u64).wrap()).collect());
94+
assert_eq!(2, do_match(pkg, &MatchOp::MTR, 0, &MatchOp::MEQ, 2, 0));
95+
}
96+
97+
#[test]
98+
fn impossible_match_returns_ones() {
99+
let pkg = &([0, 1, 2, 3].iter().map(|i| Object::Integer(*i as u64).wrap()).collect());
100+
assert_eq!(ONES, do_match(pkg, &MatchOp::MEQ, 2, &MatchOp::MLT, 1, 0));
101+
}
102+
103+
#[test]
104+
fn offset_works_correctly() {
105+
let pkg = &([1, 1, 1, 1].iter().map(|i| Object::Integer(*i as u64).wrap()).collect());
106+
assert_eq!(2, do_match(pkg, &MatchOp::MEQ, 1, &MatchOp::MTR, 0, 2));
107+
}
108+
}

‎src/aml/mod.rs‎

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@
99
* - Load and LoadTable
1010
* - DefDataRegion
1111
* - Notify
12-
* - DefMatch
1312
*
1413
* - Method recursion depth?
1514
* - Loop timeouts
1615
* - Fuzzing and guarantee panic-free interpretation
1716
*/
1817

1918
mod interrupt_model_used;
19+
mod match_expr;
20+
2021
pub mod namespace;
2122
pub mod object;
2223
pub mod op_region;
@@ -51,6 +52,7 @@ use core::{
5152
};
5253
pub use interrupt_model_used::InterruptModelUsed;
5354
use log::{error, info, trace, warn};
55+
use match_expr::{MatchOp, do_match};
5456
use namespace::{AmlName, Namespace, NamespaceLevelKind};
5557
use object::{
5658
DeviceStatus,
@@ -1076,6 +1078,35 @@ where
10761078
context.retire_op(op);
10771079
}
10781080
}
1081+
Opcode::Match => {
1082+
extract_args!(op => [
1083+
Argument::Object(search_pkg),
1084+
Argument::MatchOp(opcode_a),
1085+
Argument::Object(operand_a),
1086+
Argument::MatchOp(opcode_b),
1087+
Argument::Object(operand_b),
1088+
Argument::Object(start_index)]
1089+
);
1090+
1091+
let Object::Package(ref pkg) = **search_pkg else {
1092+
return Err(AmlError::ObjectNotOfExpectedType {
1093+
expected: ObjectType::Package,
1094+
got: search_pkg.typ(),
1095+
});
1096+
};
1097+
1098+
let result = do_match(
1099+
pkg,
1100+
opcode_a,
1101+
operand_a.as_integer()?,
1102+
opcode_b,
1103+
operand_b.as_integer()?,
1104+
start_index.as_integer()?,
1105+
);
1106+
1107+
context.contribute_arg(Argument::Object(Object::Integer(result).wrap()));
1108+
context.retire_op(op);
1109+
}
10791110
_ => panic!("Unexpected operation has created in-flight op!"),
10801111
}
10811112
}
@@ -1212,6 +1243,13 @@ where
12121243
Opcode::One => {
12131244
context.contribute_arg(Argument::Object(Object::Integer(1).wrap()));
12141245
}
1246+
Opcode::MatchOp(opcode) => {
1247+
if context.expecting_match_opcode() {
1248+
context.contribute_arg(Argument::MatchOp(opcode));
1249+
} else {
1250+
Err(AmlError::IllegalOpcode(opcode as u16))?
1251+
}
1252+
}
12151253
Opcode::Ones => {
12161254
context.contribute_arg(Argument::Object(Object::Integer(u64::MAX).wrap()));
12171255
}
@@ -1676,6 +1714,7 @@ where
16761714
ResolveBehaviour::Placeholder => {
16771715
panic!("Invalid resolve behaviour for name to be resolved!")
16781716
}
1717+
ResolveBehaviour::MatchOpcode => Err(AmlError::InvalidArgType)?,
16791718
}
16801719
}
16811720

@@ -1726,14 +1765,17 @@ where
17261765
opcode,
17271766
&[ResolveBehaviour::TermArg, ResolveBehaviour::TermArg, ResolveBehaviour::Target],
17281767
)),
1729-
/*
1730-
* TODO
1731-
* Match is a difficult opcode to parse, as it interleaves dynamic arguments and
1732-
* random bytes that need to be extracted as you go. I think we'll need to use 1+
1733-
* internal in-flight ops to parse the static bytedatas as we go, and then retire
1734-
* the real op at the end.
1735-
*/
1736-
Opcode::Match => todo!(),
1768+
Opcode::Match => context.start(OpInFlight::new(
1769+
opcode,
1770+
&[
1771+
ResolveBehaviour::TermArg,
1772+
ResolveBehaviour::MatchOpcode,
1773+
ResolveBehaviour::TermArg,
1774+
ResolveBehaviour::MatchOpcode,
1775+
ResolveBehaviour::TermArg,
1776+
ResolveBehaviour::TermArg,
1777+
],
1778+
)),
17371779

17381780
Opcode::CreateBitField
17391781
| Opcode::CreateByteField
@@ -2955,6 +2997,8 @@ enum ResolveBehaviour {
29552997
/// Used with [`OpInFlight::new_with`] to represent arguments that have already been resolved
29562998
/// when an operation enters flight.
29572999
Placeholder,
3000+
/// The match statement takes two single byte arguments - the "match opcode".
3001+
MatchOpcode,
29583002
}
29593003

29603004
#[derive(Debug)]
@@ -2973,6 +3017,7 @@ enum Argument {
29733017
DWordData(u32),
29743018
TrackedPc(usize),
29753019
PkgLength(usize),
3020+
MatchOp(MatchOp),
29763021
}
29773022

29783023
impl OpInFlight {
@@ -3114,9 +3159,28 @@ impl MethodContext {
31143159
other => other as u16,
31153160
};
31163161

3162+
// Opcodes Zero and One overlap with the Match Opcodes that are only used in the match
3163+
// expression. It's reasonable to assume that if Zero or One appear at the time we expect a
3164+
// Match Opcode, then they *are* Match Opcodes instead of regular arguments.
31173165
Ok(match opcode {
3118-
0x00 => Opcode::Zero,
3119-
0x01 => Opcode::One,
3166+
0x00 => {
3167+
if self.expecting_match_opcode() {
3168+
Opcode::MatchOp(MatchOp::MTR)
3169+
} else {
3170+
Opcode::Zero
3171+
}
3172+
}
3173+
0x01 => {
3174+
if self.expecting_match_opcode() {
3175+
Opcode::MatchOp(MatchOp::MEQ)
3176+
} else {
3177+
Opcode::One
3178+
}
3179+
}
3180+
0x02 => Opcode::MatchOp(MatchOp::MLE),
3181+
0x03 => Opcode::MatchOp(MatchOp::MLT),
3182+
0x04 => Opcode::MatchOp(MatchOp::MGE),
3183+
0x05 => Opcode::MatchOp(MatchOp::MGT),
31203184
0x06 => Opcode::Alias,
31213185
0x08 => Opcode::Name,
31223186
0x0a => Opcode::BytePrefix,
@@ -3362,12 +3426,18 @@ impl MethodContext {
33623426

33633427
Ok(self.current_block.stream()[self.current_block.pc])
33643428
}
3429+
3430+
fn expecting_match_opcode(&self) -> bool {
3431+
let resolve_behaviour = self.in_flight.last().map(|op| op.resolve_behaviour());
3432+
resolve_behaviour.is_some_and(|rb| matches!(rb, ResolveBehaviour::MatchOpcode))
3433+
}
33653434
}
33663435

33673436
#[derive(Clone, Copy, PartialEq, Debug)]
33683437
enum Opcode {
33693438
Zero,
33703439
One,
3440+
MatchOp(MatchOp),
33713441
Alias,
33723442
Name,
33733443
BytePrefix,
@@ -3590,6 +3660,12 @@ pub enum AmlError {
35903660

35913661
/// The maximum length of time a loop ([`LOOP_TIMEOUT_NS`]) can execute for has been exceeded.
35923662
LoopTimeout,
3663+
3664+
/// The provided argument to the current operation is invalid.
3665+
///
3666+
/// For example, trying to pass an object as the Operation parameter in a Match expression is
3667+
/// invalid.
3668+
InvalidArgType,
35933669
}
35943670

35953671
#[derive(Debug, Clone, Copy, PartialEq)]

‎src/aml/object.rs‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ use static_assertions::assert_impl_all;
1111

1212
type NativeMethod = dyn Fn(&[WrappedObject]) -> Result<WrappedObject, AmlError> + Send + Sync;
1313

14+
pub type Package = Vec<WrappedObject>;
15+
1416
#[derive(Clone)]
1517
pub enum Object {
1618
Uninitialized,
@@ -26,7 +28,7 @@ pub enum Object {
2628
Reference { kind: ReferenceKind, inner: WrappedObject },
2729
NamePath { name: AmlName, scope: AmlName },
2830
OpRegion(OpRegion),
29-
Package(Vec<WrappedObject>),
31+
Package(Package),
3032
PowerResource { system_level: u8, resource_order: u16 },
3133
Processor { proc_id: u8, pblk_address: u32, pblk_length: u8 },
3234
RawDataBuffer,

‎tests/match.asl‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
DefinitionBlock("", "DSDT", 1, "RSACPI", "MATCH", 1) {
2+
Name (AAAA, Package (4) { 0x01, 0x01, 0x02, 0x03 })
3+
Name (BBBB, Package (4) { 0x01, 0x01, 0x01, 0x01 })
4+
5+
Name(FCNT, 0)
6+
7+
Method (CHEK, 2) {
8+
If (Arg0 != Arg1) {
9+
FCNT++
10+
}
11+
}
12+
13+
Method (MAIN) {
14+
// Test all operations in first position.
15+
CHEK(2, Match (AAAA, MEQ, 2, MTR, 0, 0))
16+
CHEK(Ones, Match (AAAA, MLT, 1, MTR, 0, 0))
17+
CHEK(0, Match (AAAA, MLE, 1, MTR, 0, 0))
18+
CHEK(3, Match (AAAA, MGT, 2, MTR, 0, 0))
19+
CHEK(2, Match (AAAA, MGE, 2, MTR, 0, 0))
20+
21+
// Test all operations in second position.
22+
CHEK(2, Match (AAAA, MTR, 0, MEQ, 2, 0))
23+
CHEK(Ones, Match (AAAA, MTR, 0, MLT, 1, 0))
24+
CHEK(0, Match (AAAA, MTR, 0, MLE, 1, 0))
25+
CHEK(3, Match (AAAA, MTR, 0, MGT, 2, 0))
26+
CHEK(2, Match (AAAA, MTR, 0, MGE, 2, 0))
27+
28+
// Check offset works
29+
CHEK(2, Match (BBBB, MEQ, 1, MTR, 0, 2))
30+
CHEK(Ones, Match (BBBB, MEQ, 2, MTR, 0, 0))
31+
32+
Return (FCNT)
33+
}
34+
}

0 commit comments

Comments
 (0)