mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-02 02:49:28 +00:00
189 lines
6.0 KiB
Plaintext
189 lines
6.0 KiB
Plaintext
//
|
|
// 8088Tests.m
|
|
// Clock SignalTests
|
|
//
|
|
// Created by Thomas Harte on 13/09/2023.
|
|
// Copyright © 2023 Thomas Harte. All rights reserved.
|
|
//
|
|
|
|
#import <XCTest/XCTest.h>
|
|
|
|
#include <array>
|
|
#include <cassert>
|
|
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <fstream>
|
|
|
|
#include "NSData+dataWithContentsOfGZippedFile.h"
|
|
|
|
#include "../../../InstructionSets/x86/Decoder.hpp"
|
|
|
|
namespace {
|
|
|
|
// The tests themselves are not duplicated in this repository;
|
|
// provide their real path here.
|
|
constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1";
|
|
|
|
}
|
|
|
|
@interface i8088Tests : XCTestCase
|
|
@end
|
|
|
|
@implementation i8088Tests
|
|
|
|
- (NSArray<NSString *> *)testFiles {
|
|
NSString *path = [NSString stringWithUTF8String:TestSuiteHome];
|
|
NSSet *allowList = [NSSet setWithArray:@[
|
|
]];
|
|
|
|
NSSet *ignoreList = nil;
|
|
|
|
NSArray<NSString *> *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
|
|
files = [files filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSString* evaluatedObject, NSDictionary<NSString *,id> *) {
|
|
if(allowList.count && ![allowList containsObject:[evaluatedObject lastPathComponent]]) {
|
|
return NO;
|
|
}
|
|
if([ignoreList containsObject:[evaluatedObject lastPathComponent]]) {
|
|
return NO;
|
|
}
|
|
return [evaluatedObject hasSuffix:@"json.gz"];
|
|
}]];
|
|
|
|
NSMutableArray<NSString *> *fullPaths = [[NSMutableArray alloc] init];
|
|
for(NSString *file in files) {
|
|
[fullPaths addObject:[path stringByAppendingPathComponent:file]];
|
|
}
|
|
|
|
return [fullPaths sortedArrayUsingSelector:@selector(compare:)];
|
|
}
|
|
|
|
- (NSString *)toString:(const InstructionSet::x86::Instruction<false> &)instruction offsetLength:(int)offsetLength immediateLength:(int)immediateLength {
|
|
const auto operation = to_string(instruction, InstructionSet::x86::Model::i8086, offsetLength, immediateLength);
|
|
return [[NSString stringWithUTF8String:operation.c_str()] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
|
}
|
|
|
|
- (bool)applyDecodingTest:(NSDictionary *)test file:(NSString *)file assert:(BOOL)assert {
|
|
using Decoder = InstructionSet::x86::Decoder<InstructionSet::x86::Model::i8086>;
|
|
Decoder decoder;
|
|
|
|
// Build a vector of the instruction bytes; this makes manual step debugging easier.
|
|
NSArray<NSNumber *> *encoding = test[@"bytes"];
|
|
std::vector<uint8_t> data;
|
|
data.reserve(encoding.count);
|
|
for(NSNumber *number in encoding) {
|
|
data.push_back([number intValue]);
|
|
}
|
|
auto hex_instruction = [&]() -> NSString * {
|
|
NSMutableString *hexInstruction = [[NSMutableString alloc] init];
|
|
for(uint8_t byte: data) {
|
|
[hexInstruction appendFormat:@"%02x ", byte];
|
|
}
|
|
return hexInstruction;
|
|
};
|
|
|
|
const auto decoded = decoder.decode(data.data(), data.size());
|
|
if(assert) {
|
|
XCTAssert(
|
|
decoded.first == [encoding count],
|
|
"Wrong length of instruction decoded for %@ — decoded %d rather than %lu from %@; file %@",
|
|
test[@"name"],
|
|
decoded.first,
|
|
(unsigned long)[encoding count],
|
|
hex_instruction(),
|
|
file
|
|
);
|
|
}
|
|
if(decoded.first != [encoding count]) {
|
|
return false;
|
|
}
|
|
|
|
// The decoder doesn't preserve the original offset length, which makes no functional difference but
|
|
// does affect the way that offsets are printed in the test set.
|
|
NSSet<NSString *> *decodings = [NSSet setWithObjects:
|
|
[self toString:decoded.second offsetLength:4 immediateLength:4],
|
|
[self toString:decoded.second offsetLength:2 immediateLength:4],
|
|
[self toString:decoded.second offsetLength:0 immediateLength:4],
|
|
[self toString:decoded.second offsetLength:4 immediateLength:2],
|
|
[self toString:decoded.second offsetLength:2 immediateLength:2],
|
|
[self toString:decoded.second offsetLength:0 immediateLength:2],
|
|
nil];
|
|
|
|
auto compare_decoding = [&](NSString *name) -> bool {
|
|
return [decodings containsObject:name];
|
|
};
|
|
|
|
bool isEqual = compare_decoding(test[@"name"]);
|
|
|
|
// Attempt clerical reconciliation:
|
|
//
|
|
// TEMPORARY HACK: the test set incorrectly states 'bp+si' whenever it means 'bp+di'.
|
|
// Though it also uses 'bp+si' correctly when it means 'bp+si'. Until fixed, take
|
|
// a pass on potential issues there.
|
|
//
|
|
// SEPARATELY: The test suite retains a distinction between SHL and SAL, which the decoder doesn't. So consider that
|
|
// a potential point of difference.
|
|
//
|
|
// Also, the decoder treats INT3 and INT 3 as the same thing. So allow for a meshing of those.
|
|
int adjustment = 7;
|
|
while(!isEqual && adjustment) {
|
|
NSString *alteredName = [test[@"name"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
|
|
|
if(adjustment & 4) {
|
|
alteredName = [alteredName stringByReplacingOccurrencesOfString:@"bp+si" withString:@"bp+di"];
|
|
}
|
|
if(adjustment & 2) {
|
|
alteredName = [alteredName stringByReplacingOccurrencesOfString:@"shl" withString:@"sal"];
|
|
}
|
|
if(adjustment & 1) {
|
|
alteredName = [alteredName stringByReplacingOccurrencesOfString:@"int3" withString:@"int 03h"];
|
|
}
|
|
|
|
isEqual = compare_decoding(alteredName);
|
|
--adjustment;
|
|
}
|
|
|
|
if(assert) {
|
|
XCTAssert(
|
|
isEqual,
|
|
"%@ doesn't match %@ or similar, was %@ within %@",
|
|
test[@"name"],
|
|
[decodings anyObject],
|
|
hex_instruction(),
|
|
file
|
|
);
|
|
}
|
|
|
|
return isEqual;
|
|
}
|
|
|
|
- (void)testDecoding {
|
|
NSMutableSet<NSString *> *failures = [[NSMutableSet alloc] init];
|
|
NSArray<NSString *> *testFiles = [self testFiles];
|
|
|
|
for(NSString *file in testFiles) {
|
|
NSData *data = [NSData dataWithContentsOfGZippedFile:file];
|
|
NSArray<NSDictionary *> *testsInFile = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
|
|
NSUInteger successes = 0;
|
|
for(NSDictionary *test in testsInFile) {
|
|
// A single failure per instruction is fine.
|
|
if(![self applyDecodingTest:test file:file assert:YES]) {
|
|
[failures addObject:file];
|
|
|
|
// Attempt a second decoding, to provide a debugger hook.
|
|
[self applyDecodingTest:test file:file assert:NO];
|
|
|
|
break;
|
|
}
|
|
++successes;
|
|
}
|
|
if(successes != [testsInFile count]) {
|
|
NSLog(@"Failed after %ld successes", successes);
|
|
}
|
|
}
|
|
|
|
NSLog(@"%ld failures out of %ld tests: %@", failures.count, testFiles.count, [[failures allObjects] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]);
|
|
}
|
|
|
|
@end
|