feat(assets): sample native resource device coefficient
Docs Deploy / Build and Deploy MkDocs (push) Successful in 1m12s
Docs Deploy / Build and Deploy MkDocs (push) Successful in 1m12s
This commit is contained in:
4 files changed
+509
-8
No files matched your search
@@ -1180,6 +1180,19 @@ impl Default for ControlTag21InstanceState {
|
||||
/// Input or metadata prevented a safe native control sample.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ControlSamplingError {
|
||||
/// This resource-device helper only implements typed-record tag 26.
|
||||
UnsupportedResourceDeviceTag { actual: u32 },
|
||||
/// The record's raw provider key has no resolved NDP table.
|
||||
MissingResourceDeviceNodeData { key: u32 },
|
||||
/// The record's raw provider key is not a row in its NDP table.
|
||||
ResourceDeviceNodeDataKeyOutOfRange { key: u32, row_count: usize },
|
||||
/// The caller did not supply the provider's current type-1 value.
|
||||
MissingResourceDeviceProviderValue { key: u32 },
|
||||
/// A coefficient is outside the helper's finite normal-or-zero contract.
|
||||
InvalidResourceDeviceOperand { operand: &'static str },
|
||||
/// The native x87 intermediate is outside the range represented by normal
|
||||
/// f32 values, so the Rust result could differ from the original.
|
||||
UnsupportedResourceDeviceFloatRange { stage: &'static str },
|
||||
/// A state object was reused with a different model node count.
|
||||
StateNodeCountMismatch { expected: usize, actual: usize },
|
||||
/// A state object was reused with a different number of tag-21 records.
|
||||
@@ -1216,6 +1229,74 @@ pub enum ControlSamplingError {
|
||||
NonFiniteSample { record_index: usize },
|
||||
}
|
||||
|
||||
/// Samples Control's tag-26 typed-record query `0x300` for one current state.
|
||||
///
|
||||
/// The key in fixed word 1 must select an existing NDP row. The caller supplies
|
||||
/// that provider's current type-1 value and the object's current multiplier;
|
||||
/// this function does not invent their runtime state. Fixed word 11 is the
|
||||
/// coefficient loaded by selector `0x300`. Native code multiplies in the order
|
||||
/// `provider_now * coefficient * multiplier_now`.
|
||||
///
|
||||
/// The source x87 code returns a value with an extended exponent range even
|
||||
/// though it uses 24-bit precision. This f32 helper therefore accepts only
|
||||
/// finite normal-or-zero operands and normal-or-exact-zero intermediate and
|
||||
/// final products. It rejects subnormal or out-of-f32-range products rather
|
||||
/// than silently underflowing or overflowing differently from x87.
|
||||
pub fn sample_control_resource_device_selector_300(
|
||||
record: &PreparedControlTypedRecord,
|
||||
node_data: Option<&PreparedControlNodeDataTable>,
|
||||
provider_now: Option<f32>,
|
||||
multiplier_now: f32,
|
||||
) -> Result<f32, ControlSamplingError> {
|
||||
if record.tag != 26 {
|
||||
return Err(ControlSamplingError::UnsupportedResourceDeviceTag { actual: record.tag });
|
||||
}
|
||||
|
||||
let key = record.fixed_words[1];
|
||||
let Some(node_data) = node_data else {
|
||||
return Err(ControlSamplingError::MissingResourceDeviceNodeData { key });
|
||||
};
|
||||
if !usize::try_from(key).is_ok_and(|key| key < node_data.rows.len()) {
|
||||
return Err(ControlSamplingError::ResourceDeviceNodeDataKeyOutOfRange {
|
||||
key,
|
||||
row_count: node_data.rows.len(),
|
||||
});
|
||||
}
|
||||
let provider_now =
|
||||
provider_now.ok_or(ControlSamplingError::MissingResourceDeviceProviderValue { key })?;
|
||||
let base = f32::from_bits(record.fixed_words[11]);
|
||||
|
||||
for (operand, value) in [
|
||||
("provider", provider_now),
|
||||
("base coefficient", base),
|
||||
("multiplier", multiplier_now),
|
||||
] {
|
||||
if !value.is_finite() || (value != 0.0 && !value.is_normal()) {
|
||||
return Err(ControlSamplingError::InvalidResourceDeviceOperand { operand });
|
||||
}
|
||||
}
|
||||
|
||||
let provider_times_base =
|
||||
native_range_compatible_product(provider_now, base, "provider times base")?;
|
||||
native_range_compatible_product(provider_times_base, multiplier_now, "then multiplier")
|
||||
}
|
||||
|
||||
fn native_range_compatible_product(
|
||||
lhs: f32,
|
||||
rhs: f32,
|
||||
stage: &'static str,
|
||||
) -> Result<f32, ControlSamplingError> {
|
||||
let exact = f64::from(lhs) * f64::from(rhs);
|
||||
let absolute = exact.abs();
|
||||
let minimum_normal = f64::from(f32::MIN_POSITIVE);
|
||||
let maximum = f64::from(f32::MAX);
|
||||
if exact != 0.0 && (absolute < minimum_normal || absolute > maximum) {
|
||||
return Err(ControlSamplingError::UnsupportedResourceDeviceFloatRange { stage });
|
||||
}
|
||||
|
||||
Ok(lhs * rhs)
|
||||
}
|
||||
|
||||
/// Samples per-node animation times using the decoded Control timeline.
|
||||
///
|
||||
/// `native_clock_ms` is the absolute u32 millisecond clock passed to native
|
||||
@@ -4141,6 +4222,203 @@ mod tests {
|
||||
// Preserve the authored CTLD fixture bits; this is not the `f32` value of tau.
|
||||
const AUTHORED_TAG21_RAW_PARAMETER_3: f32 = f32::from_bits(0x40C8_F5C3);
|
||||
|
||||
fn resource_device_record(tag: u32, key: u32, base_bits: u32) -> PreparedControlTypedRecord {
|
||||
let mut fixed_words = [0; 44];
|
||||
fixed_words[1] = key;
|
||||
fixed_words[11] = base_bits;
|
||||
PreparedControlTypedRecord {
|
||||
tag,
|
||||
fixed_words,
|
||||
values: Vec::new(),
|
||||
string_bytes: Vec::new(),
|
||||
gate_provider: PreparedControlGateProvider::NotApplicable,
|
||||
}
|
||||
}
|
||||
|
||||
fn resource_device_node_data_table(row_count: usize) -> PreparedControlNodeDataTable {
|
||||
PreparedControlNodeDataTable {
|
||||
source: ResourceKey {
|
||||
archive: parse_path("parts.rlb").expect("NDP archive"),
|
||||
name: resource_name(b"resource-device.ndp"),
|
||||
type_id: None,
|
||||
},
|
||||
rows: vec![
|
||||
PreparedControlNodeDataRow {
|
||||
raw_flags: 0,
|
||||
max_volume: 0.0,
|
||||
mobility_per_volume: 0.0,
|
||||
effect: PreparedControlResourcePair {
|
||||
archive: [0; 32],
|
||||
name: [0; 32],
|
||||
},
|
||||
};
|
||||
row_count
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag26_selector_300_multiplies_provider_then_fixed_base_then_multiplier() {
|
||||
let record = resource_device_record(26, 1, 0x3D25_AE4A);
|
||||
let node_data = resource_device_node_data_table(2);
|
||||
let provider_now = f32::from_bits(0x3FE6_982F);
|
||||
let multiplier_now = f32::from_bits(0x4071_5539);
|
||||
|
||||
let sampled = sample_control_resource_device_selector_300(
|
||||
&record,
|
||||
Some(&node_data),
|
||||
Some(provider_now),
|
||||
multiplier_now,
|
||||
)
|
||||
.expect("valid tag-26 resource-device sample");
|
||||
|
||||
assert_eq!(sampled.to_bits(), 0x3E8C_B02E);
|
||||
assert_ne!(
|
||||
sampled.to_bits(),
|
||||
((provider_now * multiplier_now) * f32::from_bits(record.fixed_words[11])).to_bits(),
|
||||
"the original selector multiplies the provider by the record base first"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag26_selector_300_requires_its_ndp_row_and_explicit_provider() {
|
||||
let record = resource_device_record(26, 1, 2.0_f32.to_bits());
|
||||
let one_row = resource_device_node_data_table(1);
|
||||
assert_eq!(
|
||||
sample_control_resource_device_selector_300(&record, Some(&one_row), Some(3.0), 4.0,),
|
||||
Err(ControlSamplingError::ResourceDeviceNodeDataKeyOutOfRange {
|
||||
key: 1,
|
||||
row_count: 1,
|
||||
})
|
||||
);
|
||||
|
||||
let two_rows = resource_device_node_data_table(2);
|
||||
assert_eq!(
|
||||
sample_control_resource_device_selector_300(&record, Some(&two_rows), None, 4.0),
|
||||
Err(ControlSamplingError::MissingResourceDeviceProviderValue { key: 1 })
|
||||
);
|
||||
assert_eq!(
|
||||
sample_control_resource_device_selector_300(&record, None, Some(3.0), 4.0),
|
||||
Err(ControlSamplingError::MissingResourceDeviceNodeData { key: 1 })
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag26_selector_300_rejects_other_tags_and_extended_x87_ranges() {
|
||||
let node_data = resource_device_node_data_table(1);
|
||||
let tag21 = resource_device_record(21, 0, 1.0_f32.to_bits());
|
||||
assert_eq!(
|
||||
sample_control_resource_device_selector_300(&tag21, Some(&node_data), Some(1.0), 1.0),
|
||||
Err(ControlSamplingError::UnsupportedResourceDeviceTag { actual: 21 })
|
||||
);
|
||||
|
||||
let tag26 = resource_device_record(26, 0, f32::MIN_POSITIVE.to_bits());
|
||||
assert_eq!(
|
||||
sample_control_resource_device_selector_300(
|
||||
&tag26,
|
||||
Some(&node_data),
|
||||
Some(f32::MIN_POSITIVE),
|
||||
1.0,
|
||||
),
|
||||
Err(ControlSamplingError::UnsupportedResourceDeviceFloatRange {
|
||||
stage: "provider times base",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag26_selector_300_preserves_negative_and_signed_zero_values() {
|
||||
let node_data = resource_device_node_data_table(1);
|
||||
let record = resource_device_record(26, 0, 0.5_f32.to_bits());
|
||||
let negative =
|
||||
sample_control_resource_device_selector_300(&record, Some(&node_data), Some(-2.0), 4.0)
|
||||
.expect("negative factors are not clamped");
|
||||
assert_eq!(negative, -4.0);
|
||||
|
||||
let negative_zero = sample_control_resource_device_selector_300(
|
||||
&resource_device_record(26, 0, 2.0_f32.to_bits()),
|
||||
Some(&node_data),
|
||||
Some(-0.0),
|
||||
3.0,
|
||||
)
|
||||
.expect("signed zero is an exact native input");
|
||||
assert_eq!(negative_zero.to_bits(), (-0.0_f32).to_bits());
|
||||
|
||||
let positive_zero = sample_control_resource_device_selector_300(
|
||||
&resource_device_record(26, 0, 2.0_f32.to_bits()),
|
||||
Some(&node_data),
|
||||
Some(0.0),
|
||||
3.0,
|
||||
)
|
||||
.expect("positive zero is an exact native input");
|
||||
assert_eq!(positive_zero.to_bits(), 0.0_f32.to_bits());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag26_selector_300_rejects_nonfinite_and_subnormal_operands() {
|
||||
let node_data = resource_device_node_data_table(1);
|
||||
for (operand, provider_now, multiplier_now, base_bits) in [
|
||||
("provider", f32::NAN, 1.0, 1.0_f32.to_bits()),
|
||||
("multiplier", 1.0, f32::INFINITY, 1.0_f32.to_bits()),
|
||||
("base coefficient", 1.0, 1.0, 1),
|
||||
] {
|
||||
let record = resource_device_record(26, 0, base_bits);
|
||||
assert_eq!(
|
||||
sample_control_resource_device_selector_300(
|
||||
&record,
|
||||
Some(&node_data),
|
||||
Some(provider_now),
|
||||
multiplier_now,
|
||||
),
|
||||
Err(ControlSamplingError::InvalidResourceDeviceOperand { operand })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag26_selector_300_rejects_extended_intermediate_and_final_ranges() {
|
||||
let node_data = resource_device_node_data_table(1);
|
||||
let provider = f32::MAX;
|
||||
let overflowing_intermediate = resource_device_record(26, 0, 2.0_f32.to_bits());
|
||||
assert_eq!(
|
||||
sample_control_resource_device_selector_300(
|
||||
&overflowing_intermediate,
|
||||
Some(&node_data),
|
||||
Some(provider),
|
||||
0.25,
|
||||
),
|
||||
Err(ControlSamplingError::UnsupportedResourceDeviceFloatRange {
|
||||
stage: "provider times base",
|
||||
})
|
||||
);
|
||||
|
||||
let underflowing_intermediate = resource_device_record(26, 0, f32::MIN_POSITIVE.to_bits());
|
||||
assert_eq!(
|
||||
sample_control_resource_device_selector_300(
|
||||
&underflowing_intermediate,
|
||||
Some(&node_data),
|
||||
Some(f32::MIN_POSITIVE),
|
||||
f32::MAX,
|
||||
),
|
||||
Err(ControlSamplingError::UnsupportedResourceDeviceFloatRange {
|
||||
stage: "provider times base",
|
||||
})
|
||||
);
|
||||
|
||||
let subnormal_final = resource_device_record(26, 0, 1.0_f32.to_bits());
|
||||
assert_eq!(
|
||||
sample_control_resource_device_selector_300(
|
||||
&subnormal_final,
|
||||
Some(&node_data),
|
||||
Some(f32::MIN_POSITIVE),
|
||||
0.5,
|
||||
),
|
||||
Err(ControlSamplingError::UnsupportedResourceDeviceFloatRange {
|
||||
stage: "then multiplier",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
fn tag21_fullcycle_control() -> PreparedComponentControl {
|
||||
let source = ResourceKey {
|
||||
archive: parse_path("parts.rlb").expect("archive"),
|
||||
@@ -6164,6 +6442,60 @@ mod tests {
|
||||
assert_eq!(plan.lightmap_count, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires licensed corpus"]
|
||||
fn tag26_selector_300_uses_a_real_control_record_and_ndp_key() {
|
||||
let root = fixture_root("IS");
|
||||
let vfs: Arc<dyn Vfs> = Arc::new(DirectoryVfs::new(&root));
|
||||
let repository = CachedResourceRepository::new(Arc::clone(&vfs));
|
||||
let roots = [resource_name(b"UNITS/BUILDS/MINE/min_l_n1.dat")];
|
||||
let (_graph, prototypes) =
|
||||
build_prototype_graph(&repository, vfs.as_ref(), &roots).expect("prototype graph");
|
||||
let assets = prepare_mission_assets_with_repository(
|
||||
&repository,
|
||||
&[0..prototypes.len()],
|
||||
&prototypes,
|
||||
)
|
||||
.expect("prepared licensed unit controls");
|
||||
|
||||
let mut sampled_records = 0;
|
||||
for control in assets.object_component_controls.iter().flatten().flatten() {
|
||||
for record in control
|
||||
.typed_records
|
||||
.iter()
|
||||
.filter(|record| record.tag == 26)
|
||||
{
|
||||
let node_data = control
|
||||
.node_data
|
||||
.as_ref()
|
||||
.expect("tag-26 control has its explicit NDP table");
|
||||
assert_eq!(control.source.name.0, b"fr_l_mine.ctl");
|
||||
assert_eq!(node_data.source.name.0, b"fr_l_mine.ndp");
|
||||
assert_eq!(node_data.rows.len(), 22);
|
||||
assert_eq!(record.fixed_words[1], 2);
|
||||
assert_eq!(record.fixed_words[11], 0x3F80_0000);
|
||||
let sample = sample_control_resource_device_selector_300(
|
||||
record,
|
||||
Some(node_data),
|
||||
Some(1.0),
|
||||
1.0,
|
||||
)
|
||||
.expect("real tag-26 key selects an NDP row");
|
||||
assert_eq!(sample.to_bits(), record.fixed_words[11]);
|
||||
println!(
|
||||
"control={}: ndp={}: key={} rows={} coefficient=0x{:08x}",
|
||||
String::from_utf8_lossy(&control.source.name.0),
|
||||
String::from_utf8_lossy(&node_data.source.name.0),
|
||||
record.fixed_words[1],
|
||||
node_data.rows.len(),
|
||||
record.fixed_words[11],
|
||||
);
|
||||
sampled_records += 1;
|
||||
}
|
||||
}
|
||||
assert_eq!(sampled_records, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires licensed corpus"]
|
||||
fn repository_plan_deduplicates_duplicate_visuals_but_graph_preserves_requests() {
|
||||
|
||||
@@ -16,6 +16,7 @@ internal static class BehaviorDistributorTickOracle {
|
||||
private const uint MemRelease = 0x00008000;
|
||||
private const uint PageReadWrite = 0x04;
|
||||
private const string ExpectedBehaviorSha256 = "ddfbdbde07832358fe6e35e01c8dba9b58e7313789498b6049ed51d4ed9baf99";
|
||||
private const string ExpectedControlSha256 = "bcb86318089028548a178b03a21accdce15df0cc85ddd91674cf3c2f55012c6e";
|
||||
private const int TickRva = 0x19e80;
|
||||
private const int AddObjectRva = 0x1aac0;
|
||||
private const int NormalizeRva = 0x1ac90;
|
||||
@@ -23,6 +24,9 @@ internal static class BehaviorDistributorTickOracle {
|
||||
private const int IndexedPropertyGetRva = 0x0b5b0;
|
||||
private const int OreInitializationRva = 0x08790;
|
||||
private const int GetSystemArealMapIatRva = 0x59008;
|
||||
private const int ControlResourceDeviceGetterRva = 0x21d00;
|
||||
private const int ControlProviderInterfaceVtableRva = 0x3d254;
|
||||
private const int ControlProviderType1GetterRva = 0x0dc40;
|
||||
private const uint ResourceProperty = 0x02000100;
|
||||
private static Fixture active;
|
||||
|
||||
@@ -52,6 +56,7 @@ internal static class BehaviorDistributorTickOracle {
|
||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate void IndexedPropertySet(IntPtr self, uint index, uint valueBits);
|
||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int IndexedPropertyGet(IntPtr self, uint index, IntPtr resultBits);
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate void NativeOreInitialization(IntPtr self);
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate float NativeControlResourceDeviceGet(IntPtr self, uint selector);
|
||||
|
||||
private static readonly AreaMapGetter AreaMapGetterCallback = GetAreaMap;
|
||||
private static readonly VectorInsert VectorInsertCallback = InsertVector;
|
||||
@@ -306,13 +311,20 @@ internal static class BehaviorDistributorTickOracle {
|
||||
|
||||
private static int Main() {
|
||||
if (IntPtr.Size != 4) { Console.Error.WriteLine("Build with /platform:x86."); return 2; }
|
||||
string controlPath = Environment.GetEnvironmentVariable("FPARKAN_ORIGINAL_CONTROL_DLL");
|
||||
if (String.IsNullOrEmpty(controlPath)) controlPath = @"C:\GOG Games\Parkan - Iron Strategy\Control.dll";
|
||||
if (!File.Exists(controlPath)) { Console.Error.WriteLine("Set FPARKAN_ORIGINAL_CONTROL_DLL to the installed Control.dll."); return 7; }
|
||||
string controlHash = Hash(controlPath);
|
||||
if (controlHash != ExpectedControlSha256) { Console.Error.WriteLine("Unexpected Control.dll SHA-256: " + controlHash); return 8; }
|
||||
IntPtr controlModule = LoadLibraryEx(controlPath, IntPtr.Zero, LoadWithAlteredSearchPath);
|
||||
if (controlModule == IntPtr.Zero) { Console.Error.WriteLine("LoadLibraryEx(Control.dll) failed: " + Marshal.GetLastWin32Error()); return 9; }
|
||||
string path = Environment.GetEnvironmentVariable("FPARKAN_ORIGINAL_BEHAVIOR_DLL");
|
||||
if (String.IsNullOrEmpty(path)) path = @"C:\GOG Games\Parkan - Iron Strategy\Behavior.dll";
|
||||
if (!File.Exists(path)) { Console.Error.WriteLine("Set FPARKAN_ORIGINAL_BEHAVIOR_DLL to the installed Behavior.dll."); return 3; }
|
||||
if (!File.Exists(path)) { Console.Error.WriteLine("Set FPARKAN_ORIGINAL_BEHAVIOR_DLL to the installed Behavior.dll."); FreeLibrary(controlModule); return 3; }
|
||||
string hash = Hash(path);
|
||||
if (hash != ExpectedBehaviorSha256) { Console.Error.WriteLine("Unexpected Behavior.dll SHA-256: " + hash); return 4; }
|
||||
if (hash != ExpectedBehaviorSha256) { Console.Error.WriteLine("Unexpected Behavior.dll SHA-256: " + hash); FreeLibrary(controlModule); return 4; }
|
||||
IntPtr module = LoadLibraryEx(path, IntPtr.Zero, LoadWithAlteredSearchPath);
|
||||
if (module == IntPtr.Zero) { Console.Error.WriteLine("LoadLibraryEx(Behavior.dll) failed: " + Marshal.GetLastWin32Error()); return 5; }
|
||||
if (module == IntPtr.Zero) { Console.Error.WriteLine("LoadLibraryEx(Behavior.dll) failed: " + Marshal.GetLastWin32Error()); FreeLibrary(controlModule); return 5; }
|
||||
IntPtr import = IntPtr.Add(module, GetSystemArealMapIatRva);
|
||||
IntPtr originalImport = Marshal.ReadIntPtr(import);
|
||||
uint oldProtection = 0;
|
||||
@@ -334,7 +346,7 @@ internal static class BehaviorDistributorTickOracle {
|
||||
throw new InvalidOperationException("VirtualProtect(GetSystemArealMap IAT) failed: " + Marshal.GetLastWin32Error());
|
||||
protectionChanged = true;
|
||||
Marshal.WriteIntPtr(import, Marshal.GetFunctionPointerForDelegate(AreaMapGetterCallback));
|
||||
RunSmokeCases(module);
|
||||
RunSmokeCases(module, controlModule);
|
||||
Console.WriteLine("original Behavior.dll distributor oracle passed (native Add 0x1aac0, normalize 0x1ac90, tick 0x19e80; only GetSystemArealMap IAT and bounded vector insertion are substituted; this does not compare Rust parity).");
|
||||
return 0;
|
||||
}
|
||||
@@ -357,10 +369,11 @@ internal static class BehaviorDistributorTickOracle {
|
||||
throw new InvalidOperationException("VirtualFree(fastcall callback thunks) failed: " + Marshal.GetLastWin32Error());
|
||||
if (x87ControlWordCell != IntPtr.Zero) { Marshal.FreeHGlobal(x87ControlWordCell); x87ControlWordCell = IntPtr.Zero; }
|
||||
FreeLibrary(module);
|
||||
FreeLibrary(controlModule);
|
||||
}
|
||||
}
|
||||
|
||||
private static void RunSmokeCases(IntPtr module) {
|
||||
private static void RunSmokeCases(IntPtr module, IntPtr controlModule) {
|
||||
ushort savedControlWord = CurrentX87ControlWord();
|
||||
Console.WriteLine("x87-control-word-before=0x" + savedControlWord.ToString("x4") + " (x86 oracle process)");
|
||||
try {
|
||||
@@ -368,6 +381,7 @@ internal static class BehaviorDistributorTickOracle {
|
||||
Console.WriteLine("native-add-normalize-x87-control-word=0x" + CurrentX87ControlWord().ToString("x4"));
|
||||
RunNativeAddCases(module);
|
||||
RunNativeNormalizeCases(module);
|
||||
RunNativeControlResourceDeviceCase(controlModule);
|
||||
} finally {
|
||||
SetX87ControlWord(savedControlWord);
|
||||
Console.WriteLine("x87-control-word-restored=0x" + CurrentX87ControlWord().ToString("x4"));
|
||||
@@ -396,6 +410,76 @@ internal static class BehaviorDistributorTickOracle {
|
||||
RunSkippedCase(tick);
|
||||
}
|
||||
|
||||
private static void RunNativeControlResourceDeviceCase(IntPtr module) {
|
||||
Expect(CurrentX87ControlWord() == 0x007f, "Control selector 0x300 uses x87 control word 0x007f");
|
||||
const uint key = 2;
|
||||
const int rowCount = 22;
|
||||
const int providerRow = (int)key * 0x2c;
|
||||
IntPtr providerOwner = Marshal.AllocHGlobal(0x560);
|
||||
IntPtr providerRows = Marshal.AllocHGlobal(rowCount * 0x2c);
|
||||
IntPtr device = IntPtr.Zero;
|
||||
try {
|
||||
Zero(providerOwner, 0x560);
|
||||
Zero(providerRows, rowCount * 0x2c);
|
||||
IntPtr providerInterface = IntPtr.Add(providerOwner, 4);
|
||||
IntPtr providerVtable = IntPtr.Add(module, ControlProviderInterfaceVtableRva);
|
||||
Marshal.WriteIntPtr(providerInterface, providerVtable);
|
||||
Expect(
|
||||
Marshal.ReadIntPtr(providerVtable, 0x0c) == IntPtr.Add(module, ControlProviderType1GetterRva),
|
||||
"Control provider interface vtable uses the native type-1 getter");
|
||||
Marshal.WriteIntPtr(providerOwner, 0x55c, providerRows);
|
||||
|
||||
device = Marshal.AllocHGlobal(0x94);
|
||||
Zero(device, 0x94);
|
||||
Marshal.WriteInt32(device, 0x04, unchecked((int)key));
|
||||
Marshal.WriteIntPtr(device, 0x44, providerInterface);
|
||||
|
||||
NativeControlResourceDeviceGet getter = (NativeControlResourceDeviceGet)
|
||||
Marshal.GetDelegateForFunctionPointer(
|
||||
IntPtr.Add(module, ControlResourceDeviceGetterRva),
|
||||
typeof(NativeControlResourceDeviceGet));
|
||||
ExpectControlResourceDeviceResult(getter, device, providerRows, providerRow,
|
||||
0x3fe6982f, 0x3d25ae4a, 0x40715539, 0x3e8cb02e, "order-sensitive normal");
|
||||
ExpectControlResourceDeviceResult(getter, device, providerRows, providerRow,
|
||||
0x7f7fffff, 0x40000000, 0x3e800000, 0x7effffff, "extended intermediate overflow then finite");
|
||||
ExpectControlResourceDeviceResult(getter, device, providerRows, providerRow,
|
||||
0x00800000, 0x00800000, 0x7f7fffff, 0x017fffff, "extended intermediate underflow then finite");
|
||||
ExpectControlResourceDeviceResult(getter, device, providerRows, providerRow,
|
||||
0x00800000, 0x3f800000, 0x3f000000, 0x00400000, "subnormal final result");
|
||||
ExpectControlResourceDeviceResult(getter, device, providerRows, providerRow,
|
||||
0xc0000000, 0x3f000000, 0x40800000, 0xc0800000, "negative factors");
|
||||
ExpectControlResourceDeviceResult(getter, device, providerRows, providerRow,
|
||||
0x80000000, 0x40000000, 0x40400000, 0x80000000, "negative zero");
|
||||
Expect(CurrentX87ControlWord() == 0x007f, "Control selector 0x300 retains x87 control word 0x007f");
|
||||
Console.WriteLine("native-control-tag26-selector-300: controlHash=" + ExpectedControlSha256 +
|
||||
" cw=0x" + CurrentX87ControlWord().ToString("x4") + " key=2 rows=22");
|
||||
GC.KeepAlive(getter);
|
||||
} finally {
|
||||
if (device != IntPtr.Zero) Marshal.FreeHGlobal(device);
|
||||
Marshal.FreeHGlobal(providerRows);
|
||||
Marshal.FreeHGlobal(providerOwner);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ExpectControlResourceDeviceResult(
|
||||
NativeControlResourceDeviceGet getter,
|
||||
IntPtr device,
|
||||
IntPtr providerRows,
|
||||
int providerRow,
|
||||
uint providerBits,
|
||||
uint baseBits,
|
||||
uint multiplierBits,
|
||||
uint expectedBits,
|
||||
string label) {
|
||||
Marshal.WriteInt32(providerRows, providerRow + 0x14, unchecked((int)providerBits));
|
||||
Marshal.WriteInt32(device, 0x54, unchecked((int)baseBits));
|
||||
Marshal.WriteInt32(device, 0x4c, unchecked((int)multiplierBits));
|
||||
uint actualBits = unchecked((uint)FloatBits(getter(device, 0x300)));
|
||||
Expect(actualBits == expectedBits,
|
||||
"Control selector 0x300 " + label + " expected " + Hex(expectedBits) + " but got " + Hex(actualBits));
|
||||
Console.WriteLine("native-control-tag26-selector-300-" + label + ": result=" + Hex(actualBits));
|
||||
}
|
||||
|
||||
private static void RunNativeIndexedPropertyCases(IntPtr module) {
|
||||
IndexedPropertySet set = (IndexedPropertySet)Marshal.GetDelegateForFunctionPointer(
|
||||
IntPtr.Add(module, IndexedPropertySetRva), typeof(IndexedPropertySet));
|
||||
@@ -911,7 +995,7 @@ internal static class BehaviorDistributorTickOracle {
|
||||
foreach (byte value in digest) hex.Append(value.ToString("x2")); return hex.ToString();
|
||||
}
|
||||
}
|
||||
private static void Expect(bool value, string message) { if (!value) throw new InvalidOperationException("original Behavior oracle failed: " + message); }
|
||||
private static void Expect(bool value, string message) { if (!value) throw new InvalidOperationException("original native oracle failed: " + message); }
|
||||
private static void ExpectLookupOrder(Fixture fixture, params string[] keys) {
|
||||
List<string> actual = new List<string>();
|
||||
foreach (string item in fixture.Trace) if (item.StartsWith("lookup:", StringComparison.Ordinal)) actual.Add(item.Substring(7));
|
||||
|
||||
Reference in new issue
Block a user