1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
//! 0x03 Memory operations (store/load, stack, allocation)
//! * `0x0301` [store_words]:`target` `[start:end]`
//! * `0x0302` [load_words]:`target` `[start:end]`
//! * `0x0303` [get_memory_size]:`target`
//! * `0x0304` [store_floats]:`target` `[start:end]`
//! * `0x0305` [load_floats]:`target` `[start:end]`
/// TODO allocation, etc

use std::collections::HashMap;

use osiris_data::data::atomic::Word;
use osiris_data::data::composite::Array;
use osiris_data::data::identification::{Address, Area};
use osiris_data::memory::MemoryError;
use osiris_process::operation::error::{OperationError, OperationResult, promote};
use osiris_process::operation::{Operation, OperationSet};
use osiris_process::operation::scheme::{ArgumentType, InstructionScheme, OperationId};
use osiris_process::processor::Cpu;
use osiris_process::register::floating_point::Number;

pub const SET_MASK: u16 = 0x0300;
pub const STORE_WORDS: OperationId = OperationId::new(SET_MASK | 0x01);
pub const LOAD_WORDS: OperationId = OperationId::new(SET_MASK | 0x02);
pub const GET_MEMORY_SIZE: OperationId = OperationId::new(SET_MASK | 0x03);
pub const STORE_FLOATS: OperationId = OperationId::new(SET_MASK | 0x04);
pub const LOAD_FLOATS: OperationId = OperationId::new(SET_MASK | 0x05);

/// # `0x0301` [store_words]:`target` `[start:end]`
///
/// ## Target
///  - The register containing the address from whom to write to.
///
/// ## Arguments
///  - Range($start, $end)
///
/// ## Operation
///  - `MEM[REG[$target]; ($end-$start)]` = `REG[$start..=$end]`
///
/// To store only one register into memory, just make `$start == $end`.
///
/// ## Errors
///  - [OperationError::InvalidArgumentType] if the provided argument is not an [ArgumentType::Range],
///  - [OperationError::MemoryError] if a memory error occurs.
pub fn store_words(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
    let range = scheme.argument.get_range()?;
    let start = cpu.bank_get(scheme.target);
    let chunk = cpu.state.bank.slice(range);
    promote(cpu.ram_mut().copy(Address::from_word(start), Array::from(chunk)))
}

/// # 0x0302 LOAD WORDS
///
/// This page describes the instruction scheme.
///
/// ## Target
///  - The register containing the address to read from.
///
/// ## Arguments
///  - Range($start, $end)
///
/// ## Operation
///  - `REG[$start..=$end]` = `MEM[REG[$target]; ($end-$start)]`
///
/// To load only one register from memory, just make `$start == $end`.
///
/// ## Errors
///  - [OperationError::InvalidArgumentType] if the provided argument is not a [ArgumentType::Range],
///  - [OperationError::MemoryError]:[MemoryError::OutOfBounds] if a memory error occurs.
pub fn load_words(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
    let range = scheme.argument.get_range()?;
    let memory = cpu.memory();
    let ram = memory.borrow();
    let start = cpu.bank_get(scheme.target).to_u64();
    let end = start + range.count() as u64 - 1;
    let area = Area::region(Address::new(start), (end - start) as usize);
    let slice = promote(ram.slice(area))?;
    cpu.state.bank.copy(range.start, slice);
    Ok(())
}

/// # 0x0303 GET MEMORY SIZE
///
/// This page describes the instruction scheme.
///
/// ## Target
///  - The register to write the memory size.
///
/// ## Arguments
///  - None.
///
/// ## Operation
///  - `REG[$target] = MEM.len()`
///
/// ## Errors
///  - [OperationError::InvalidArgumentType] if the provided argument is not a [ArgumentType::NoArgument],
pub fn get_memory_size(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
    scheme.argument.get_no_argument()?;
    cpu.bank_set(scheme.target, Word::from_usize(cpu.memory_size()));
    Ok(())
}


/// # 0x0304 STORE FLOATS
///
/// ## Target
///  - The register containing the address from whom to write to.
///
/// ## Arguments
///  - Range($start, $end)
///
/// ## Operation
///  - copies `MEM[REG[$target]; ($end-$start)]` into `REG[$start..=$end]`
///
/// To store only one register into memory, just make `$start == $end`.
///
/// ## Errors
///  - [OperationError::InvalidArgumentType] if the provided argument is not an [ArgumentType::Range],
///  - [OperationError::MemoryError] if a memory error occurs.
pub fn store_floats(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
    let range = scheme.argument.get_range()?;
    let start = cpu.bank_get(scheme.target);
    let chunk = cpu.state.vector.slice(range);
    promote(cpu.ram_mut().copy(Address::from_word(start), Number::slice_to_array(chunk)))
}

/// # 0x0305 LOAD FLOATS
///
/// This page describes the instruction scheme.
///
/// ## Target
///  - The register containing the address to read from.
///
/// ## Arguments
///  - Range($start, $end)
///
/// ## Operation
///  - copies `FLOAT[$start..=$end]` into `MEM[REG[$target]; ($end-$start)]`
///
/// To load only one register from memory, just make `$start == $end`.
///
/// ## Errors
///  - [OperationError::InvalidArgumentType] if the provided argument is not a [ArgumentType::Range],
///  - [OperationError::MemoryError]:[MemoryError::OutOfBounds] if a memory error occurs.
pub fn load_floats(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
    let range = scheme.argument.get_range()?;
    let memory = cpu.memory();
    let ram = memory.borrow();
    let start = cpu.bank_get(scheme.target).to_u64();
    let end = start + range.count() as u64 - 1;
    let area = Area::region(Address::new(start), (end - start) as usize);
    let slice = promote(ram.slice(area))?;
    cpu.state.vector.copy(range.start, &Number::from_words_slice(slice));
    Ok(())
}

/// Returns memory operations.
///
/// ## Operations
///
/// * `0x0301` [store_words]:`target` `[start:end]`
/// * `0x0302` [load_words]:`target` `[start:end]`
/// * `0x0303` [get_memory_size]:`target`
/// * `0x0304` [store_floats]:`target` `[start:end]`
/// * `0x0305` [load_floats]:`target` `[start:end]`
pub fn operation_set() -> OperationSet {
    let mut set: OperationSet = HashMap::new();
    set.insert(
        STORE_WORDS,
        Operation::new(STORE_WORDS, "store-words".to_string(), true, ArgumentType::Range, store_words),
    );
    set.insert(
        LOAD_WORDS,
        Operation::new(LOAD_WORDS, "load-words".to_string(), true, ArgumentType::Range, load_words),
    );
    set.insert(
        GET_MEMORY_SIZE,
        Operation::new(GET_MEMORY_SIZE, "get-memory-size".to_string(), true, ArgumentType::NoArgument, get_memory_size),
    );
    set.insert(
        STORE_FLOATS,
        Operation::new(STORE_FLOATS, "store-float".to_string(), true, ArgumentType::Range, store_floats),
    );
    set.insert(
        LOAD_FLOATS,
        Operation::new(LOAD_FLOATS, "load-floats".to_string(), true, ArgumentType::Range, load_floats),
    );
    set
}