2023-09-13 19:53:38 +00:00
|
|
|
//
|
|
|
|
// 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>
|
2023-09-17 20:22:17 +00:00
|
|
|
#include <sstream>
|
2023-09-13 19:53:38 +00:00
|
|
|
#include <fstream>
|
|
|
|
|
|
|
|
#include "NSData+dataWithContentsOfGZippedFile.h"
|
|
|
|
|
2023-09-13 20:00:16 +00:00
|
|
|
#include "../../../InstructionSets/x86/Decoder.hpp"
|
|
|
|
|
2023-09-13 19:53:38 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
// The tests themselves are not duplicated in this repository;
|
|
|
|
// provide their real path here.
|
2023-09-18 19:57:26 +00:00
|
|
|
constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1";
|
2023-09-13 19:53:38 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
@interface i8088Tests : XCTestCase
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation i8088Tests
|
|
|
|
|
|
|
|
- (NSArray<NSString *> *)testFiles {
|
|
|
|
NSString *path = [NSString stringWithUTF8String:TestSuiteHome];
|
2023-09-15 20:09:04 +00:00
|
|
|
NSSet *allowList = nil;
|
|
|
|
// [[NSSet alloc] initWithArray:@[
|
|
|
|
// @"00.json.gz",
|
2023-09-18 19:57:26 +00:00
|
|
|
// ]];∂
|
2023-09-13 19:53:38 +00:00
|
|
|
|
|
|
|
NSArray<NSString *> *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
|
|
|
|
files = [files filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSString* evaluatedObject, NSDictionary<NSString *,id> *) {
|
2023-09-15 19:50:59 +00:00
|
|
|
if(allowList && ![allowList containsObject:[evaluatedObject lastPathComponent]]) {
|
|
|
|
return NO;
|
|
|
|
}
|
2023-09-13 19:53:38 +00:00
|
|
|
return [evaluatedObject hasSuffix:@"json.gz"];
|
|
|
|
}]];
|
|
|
|
|
|
|
|
NSMutableArray<NSString *> *fullPaths = [[NSMutableArray alloc] init];
|
|
|
|
for(NSString *file in files) {
|
|
|
|
[fullPaths addObject:[path stringByAppendingPathComponent:file]];
|
|
|
|
}
|
|
|
|
|
|
|
|
return fullPaths;
|
|
|
|
}
|
|
|
|
|
2023-09-13 20:08:12 +00:00
|
|
|
- (bool)applyDecodingTest:(NSDictionary *)test {
|
2023-09-13 20:00:16 +00:00
|
|
|
using Decoder = InstructionSet::x86::Decoder<InstructionSet::x86::Model::i8086>;
|
|
|
|
Decoder decoder;
|
|
|
|
|
2023-09-13 20:45:39 +00:00
|
|
|
// Build a vector of the instruction; this makes manual step debugging easier.
|
2023-09-13 20:00:16 +00:00
|
|
|
NSArray<NSNumber *> *encoding = test[@"bytes"];
|
2023-09-13 20:45:39 +00:00
|
|
|
std::vector<uint8_t> data;
|
|
|
|
data.reserve(encoding.count);
|
2023-09-13 20:00:16 +00:00
|
|
|
for(NSNumber *number in encoding) {
|
2023-09-13 20:45:39 +00:00
|
|
|
data.push_back([number intValue]);
|
2023-09-13 20:00:16 +00:00
|
|
|
}
|
|
|
|
|
2023-09-16 02:28:30 +00:00
|
|
|
const auto decoded = decoder.decode(data.data(), data.size());
|
2023-09-13 20:08:12 +00:00
|
|
|
XCTAssert(
|
2023-09-16 02:28:30 +00:00
|
|
|
decoded.first == [encoding count],
|
2023-09-13 20:08:12 +00:00
|
|
|
"Wrong length of instruction decoded for %@ — decoded %d rather than %lu",
|
|
|
|
test[@"name"],
|
2023-09-16 02:28:30 +00:00
|
|
|
decoded.first,
|
2023-09-13 20:08:12 +00:00
|
|
|
(unsigned long)[encoding count]
|
|
|
|
);
|
|
|
|
|
2023-09-17 21:09:40 +00:00
|
|
|
auto log_hex = [&] {
|
2023-09-14 19:40:40 +00:00
|
|
|
NSMutableString *hexInstruction = [[NSMutableString alloc] init];
|
|
|
|
for(uint8_t byte: data) {
|
|
|
|
[hexInstruction appendFormat:@"%02x ", byte];
|
|
|
|
}
|
|
|
|
NSLog(@"Instruction was %@", hexInstruction);
|
2023-09-17 21:09:40 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
if(decoded.first != [encoding count]) {
|
|
|
|
log_hex();
|
2023-09-14 19:40:40 +00:00
|
|
|
|
2023-09-13 20:45:39 +00:00
|
|
|
// Repeat the decoding, for ease of debugging.
|
2023-09-18 19:57:26 +00:00
|
|
|
Decoder().decode(data.data(), data.size());
|
2023-09-13 20:08:12 +00:00
|
|
|
return false;
|
|
|
|
}
|
2023-09-14 19:40:40 +00:00
|
|
|
|
2023-09-16 02:28:30 +00:00
|
|
|
// Form string version, compare.
|
|
|
|
std::string operation;
|
2023-09-17 20:29:04 +00:00
|
|
|
|
|
|
|
using Repetition = InstructionSet::x86::Repetition;
|
|
|
|
switch(decoded.second.repetition()) {
|
|
|
|
case Repetition::None: break;
|
2023-09-19 17:56:48 +00:00
|
|
|
case Repetition::RepE: operation += "repe "; break;
|
2023-09-17 20:29:04 +00:00
|
|
|
case Repetition::RepNE: operation += "repne "; break;
|
|
|
|
}
|
2023-09-16 02:28:30 +00:00
|
|
|
|
2023-09-18 19:57:26 +00:00
|
|
|
operation += to_string(decoded.second.operation, decoded.second.operation_size());
|
2023-09-16 02:28:30 +00:00
|
|
|
|
2023-09-19 17:38:08 +00:00
|
|
|
auto to_hex = [] (int value, int digits) -> std::string {
|
|
|
|
auto stream = std::stringstream();
|
|
|
|
stream << std::setfill('0') << std::uppercase << std::hex << std::setw(digits);
|
|
|
|
switch(digits) {
|
|
|
|
case 2: stream << +uint8_t(value); break;
|
|
|
|
case 4: stream << +uint16_t(value); break;
|
|
|
|
default: stream << value; break;
|
|
|
|
}
|
|
|
|
stream << 'h';
|
|
|
|
return stream.str();
|
|
|
|
};
|
|
|
|
|
|
|
|
auto to_string = [&to_hex] (InstructionSet::x86::DataPointer pointer, const auto &instruction) -> std::string {
|
2023-09-16 02:28:30 +00:00
|
|
|
std::string operand;
|
|
|
|
|
|
|
|
using Source = InstructionSet::x86::Source;
|
2023-09-22 21:27:27 +00:00
|
|
|
const Source source = pointer.source<false>();
|
2023-09-17 21:05:19 +00:00
|
|
|
switch(source) {
|
|
|
|
// to_string handles all direct register names correctly.
|
|
|
|
default: return InstructionSet::x86::to_string(source, instruction.operation_size());
|
2023-09-17 20:22:17 +00:00
|
|
|
|
2023-09-19 17:38:08 +00:00
|
|
|
case Source::Immediate:
|
|
|
|
return to_hex(
|
|
|
|
instruction.operand(),
|
|
|
|
instruction.operation_size() == InstructionSet::x86::DataSize::Byte ? 2 : 4
|
|
|
|
);
|
2023-09-17 21:05:19 +00:00
|
|
|
|
2023-09-22 15:24:33 +00:00
|
|
|
case Source::DirectAddress:
|
2023-09-22 21:27:27 +00:00
|
|
|
case Source::Indirect:
|
|
|
|
case Source::IndirectNoBase: {
|
2023-09-22 15:07:09 +00:00
|
|
|
std::stringstream stream;
|
|
|
|
|
2023-09-22 15:24:33 +00:00
|
|
|
if(!InstructionSet::x86::mnemonic_implies_data_size(instruction.operation)) {
|
|
|
|
stream << InstructionSet::x86::to_string(instruction.operation_size()) << ' ';
|
2023-09-22 15:07:09 +00:00
|
|
|
}
|
2023-09-22 15:24:33 +00:00
|
|
|
|
|
|
|
Source segment = instruction.data_segment();
|
|
|
|
if(segment == Source::None) {
|
|
|
|
segment = pointer.default_segment();
|
|
|
|
if(segment == Source::None) {
|
|
|
|
segment = Source::DS;
|
|
|
|
}
|
2023-09-22 15:07:09 +00:00
|
|
|
}
|
2023-09-22 15:24:33 +00:00
|
|
|
stream << InstructionSet::x86::to_string(segment, InstructionSet::x86::DataSize::None) << ':';
|
2023-09-22 15:07:09 +00:00
|
|
|
|
|
|
|
stream << '[';
|
2023-09-22 21:27:27 +00:00
|
|
|
switch(source) {
|
|
|
|
default: break;
|
|
|
|
case Source::Indirect:
|
|
|
|
stream << InstructionSet::x86::to_string(pointer.base(), data_size(instruction.address_size()));
|
|
|
|
stream << '+' << InstructionSet::x86::to_string(pointer.index(), data_size(instruction.address_size()));
|
|
|
|
if(instruction.offset()) {
|
|
|
|
stream << '+' << to_hex(instruction.offset(), 4);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case Source::IndirectNoBase:
|
|
|
|
stream << InstructionSet::x86::to_string(pointer.index(), data_size(instruction.address_size()));
|
|
|
|
if(instruction.offset()) {
|
|
|
|
stream << '+' << to_hex(instruction.offset(), 4);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case Source::DirectAddress:
|
|
|
|
stream << to_hex(instruction.offset(), 4);
|
|
|
|
break;
|
2023-09-22 15:24:33 +00:00
|
|
|
}
|
2023-09-22 15:07:09 +00:00
|
|
|
stream << ']';
|
|
|
|
return stream.str();
|
|
|
|
}
|
2023-09-16 02:28:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return operand;
|
|
|
|
};
|
|
|
|
|
2023-09-18 19:57:26 +00:00
|
|
|
const int operands = num_operands(decoded.second.operation);
|
2023-09-19 17:38:08 +00:00
|
|
|
const bool displacement = has_displacement(decoded.second.operation);
|
|
|
|
operation += " ";
|
2023-09-16 02:28:30 +00:00
|
|
|
if(operands > 1) {
|
2023-09-17 20:22:17 +00:00
|
|
|
operation += to_string(decoded.second.destination(), decoded.second);
|
2023-09-19 17:38:08 +00:00
|
|
|
operation += ", ";
|
2023-09-16 02:28:30 +00:00
|
|
|
}
|
|
|
|
if(operands > 0) {
|
2023-09-17 20:22:17 +00:00
|
|
|
operation += to_string(decoded.second.source(), decoded.second);
|
2023-09-16 02:28:30 +00:00
|
|
|
}
|
2023-09-19 17:38:08 +00:00
|
|
|
if(displacement) {
|
|
|
|
operation += to_hex(decoded.second.displacement(), 2);
|
2023-09-17 20:29:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const NSString *objcOperation = [NSString stringWithUTF8String:operation.c_str()];
|
|
|
|
XCTAssertEqualObjects(objcOperation, test[@"name"]);
|
2023-09-16 02:28:30 +00:00
|
|
|
|
2023-09-17 20:29:04 +00:00
|
|
|
if(![objcOperation isEqualToString:test[@"name"]]) {
|
2023-09-17 21:09:40 +00:00
|
|
|
log_hex();
|
|
|
|
|
|
|
|
// Repeat operand conversions, for debugging.
|
2023-09-22 21:27:27 +00:00
|
|
|
Decoder decoder;
|
|
|
|
const auto instruction = decoder.decode(data.data(), data.size());
|
|
|
|
const InstructionSet::x86::Source sources[] = {
|
|
|
|
instruction.second.source().source<false>(),
|
|
|
|
instruction.second.destination().source<false>(),
|
|
|
|
};
|
|
|
|
(void)sources;
|
|
|
|
|
|
|
|
const auto destination = instruction.second.destination();
|
|
|
|
to_string(destination, instruction.second);
|
|
|
|
const auto source = instruction.second.source();
|
|
|
|
to_string(source, instruction.second);
|
2023-09-17 20:29:04 +00:00
|
|
|
return false;
|
|
|
|
}
|
2023-09-13 20:08:12 +00:00
|
|
|
|
|
|
|
return true;
|
2023-09-13 20:00:16 +00:00
|
|
|
}
|
|
|
|
|
2023-09-13 19:53:38 +00:00
|
|
|
- (void)testDecoding {
|
2023-09-14 13:33:45 +00:00
|
|
|
NSMutableSet<NSString *> *failures = [[NSMutableSet alloc] init];
|
|
|
|
NSArray<NSString *> *testFiles = [self testFiles];
|
|
|
|
|
|
|
|
for(NSString *file in testFiles) {
|
2023-09-13 19:53:38 +00:00
|
|
|
NSData *data = [NSData dataWithContentsOfGZippedFile:file];
|
2023-09-13 20:00:16 +00:00
|
|
|
NSArray<NSDictionary *> *testsInFile = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
|
2023-09-13 20:09:58 +00:00
|
|
|
NSUInteger successes = 0;
|
2023-09-13 20:00:16 +00:00
|
|
|
for(NSDictionary *test in testsInFile) {
|
2023-09-13 20:08:12 +00:00
|
|
|
// A single failure per instruction is fine.
|
|
|
|
if(![self applyDecodingTest:test]) {
|
2023-09-14 13:33:45 +00:00
|
|
|
[failures addObject:file];
|
2023-09-13 20:08:12 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-09-13 20:09:58 +00:00
|
|
|
++successes;
|
|
|
|
}
|
|
|
|
if(successes != [testsInFile count]) {
|
|
|
|
NSLog(@"Failed after %ld successes", successes);
|
2023-09-13 20:00:16 +00:00
|
|
|
}
|
2023-09-13 19:53:38 +00:00
|
|
|
}
|
2023-09-14 13:33:45 +00:00
|
|
|
|
2023-09-14 16:29:49 +00:00
|
|
|
NSLog(@"%ld failures out of %ld tests: %@", failures.count, testFiles.count, [[failures allObjects] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]);
|
2023-09-13 19:53:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@end
|