https://github.com/akkartik/mu/blob/main/linux/bootstrap/013direct_addressing.cc
1
2
3 :(before "End Initialize Op Names")
4 put_new(Name, "01", "add r32 to rm32 (add)");
5
6 :(code)
7 void test_add_r32_to_r32() {
8 Reg[EAX].i = 0x10;
9 Reg[EBX].i = 1;
10 run(
11 "== code 0x1\n"
12
13 " 01 d8 \n"
14
15 );
16 CHECK_TRACE_CONTENTS(
17 "run: add EBX to r/m32\n"
18 "run: r/m32 is EAX\n"
19 "run: storing 0x00000011\n"
20 );
21 }
22
23 :(before "End Single-Byte Opcodes")
24 case 0x01: {
25 uint8_t modrm = next();
26 uint8_t arg2 = (modrm>>3)&0x7;
27 trace(Callstack_depth+1, "run") << "add " << rname(arg2) << " to r/m32" << end();
28 int32_t* signed_arg1 = effective_address(modrm);
29 int32_t signed_result = *signed_arg1 + Reg[arg2].i;
30 SF = (signed_result < 0);
31 ZF = (signed_result == 0);
32 int64_t signed_full_result = static_cast<int64_t>(*signed_arg1) + Reg[arg2].i;
33 OF = (signed_result != signed_full_result);
34
35 uint32_t unsigned_arg1 = static_cast<uint32_t>(*signed_arg1);
36 uint32_t unsigned_result = unsigned_arg1 + Reg[arg2].u;
37 uint64_t unsigned_full_result = static_cast<uint64_t>(unsigned_arg1) + Reg[arg2].u;
38 CF = (unsigned_result != unsigned_full_result);
39 trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
40 *signed_arg1 = signed_result;
41 trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *signed_arg1 << end();
42 break;
43 }
44
45 :(code)
46 void test_add_r32_to_r32_signed_overflow() {
47 Reg[EAX].i = INT32_MAX;
48 Reg[EBX].i = 1;
49 run(
50 "== code 0x1\n"
51
52 " 01 d8 \n"
53
54 );
55 CHECK_TRACE_CONTENTS(
56 "run: add EBX to r/m32\n"
57 "run: r/m32 is EAX\n"
58 "run: SF=1; ZF=0; CF=0; OF=1\n"
59 "run: storing 0x80000000\n"
60 );
61 }
62
63 void test_add_r32_to_r32_unsigned_overflow() {
64 Reg[EAX].u = UINT32_MAX;
65 Reg[EBX].u = 1;
66 run(
67 "== code 0x1\n"
68
69 " 01 d8 \n"
70
71 );
72 CHECK_TRACE_CONTENTS(
73 "run: add EBX to r/m32\n"
74 "run: r/m32 is EAX\n"
75 "run: SF=0; ZF=1; CF=1; OF=0\n"
76 "run: storing 0x00000000\n"
77 );
78 }
79
80 void test_add_r32_to_r32_unsigned_and_signed_overflow() {
81 Reg[EAX].i = Reg[EBX].i = INT32_MIN;
82 run(
83 "== code 0x1\n"
84
85 " 01 d8 \n"
86
87 );
88 CHECK_TRACE_CONTENTS(
89 "run: add EBX to r/m32\n"
90 "run: r/m32 is EAX\n"
91 "run: SF=0; ZF=1; CF=1; OF=1\n"
92 "run: storing 0x00000000\n"
93 );
94 }
95
96 :(code)
97
98
99
100
101 int32_t* effective_address(uint8_t modrm) {
102 const uint8_t mod = (modrm>>6);
103
104 const uint8_t rm = modrm & 0x7;
105 if (mod == 3) {
106
107 trace(Callstack_depth+1, "run") << "r/m32 is " << rname(rm) << end();
108 return &Reg[rm].i;
109 }
110 uint32_t addr = effective_address_number(modrm);
111 trace(Callstack_depth+1, "run") << "effective address contains 0x" << HEXWORD << read_mem_i32(addr) << end();
112 return mem_addr_i32(addr);
113 }
114
115
116 uint32_t effective_address_number(uint8_t modrm) {
117 const uint8_t mod = (modrm>>6);
118
119 const uint8_t rm = modrm & 0x7;
120 uint32_t addr = 0;
121 switch (mod) {
122 case 3:
123
124 raise << "unexpected direct addressing mode\n" << end();
125 return 0;
126
127 default:
128 cerr << "unrecognized mod bits: " << NUM(mod) << '\n';
129 exit(1);
130 }
131
132
133 return addr;
134 }
135
136 string rname(uint8_t r) {
137 switch (r) {
138 case 0: return "EAX";
139 case 1: return "ECX";
140 case 2: return "EDX";
141 case 3: return "EBX";
142 case 4: return "ESP";
143 case 5: return "EBP";
144 case 6: return "ESI";
145 case 7: return "EDI";
146 default: raise << "invalid register " << r << '\n' << end(); return "";
147 }
148 }
149
150
151
152 :(before "End Initialize Op Names")
153 put_new(Name, "29", "subtract r32 from rm32 (sub)");
154
155 :(code)
156 void test_subtract_r32_from_r32() {
157 Reg[EAX].i = 10;
158 Reg[EBX].i = 1;
159 run(
160 "== code 0x1\n"
161
162 " 29 d8 \n"
163
164 );
165 CHECK_TRACE_CONTENTS(
166 "run: subtract EBX from r/m32\n"
167 "run: r/m32 is EAX\n"
168 "run: storing 0x00000009\n"
169 );
170 }
171
172 :(before "End Single-Byte Opcodes")
173 case 0x29: {
174 const uint8_t modrm = next();
175 const uint8_t arg2 = (modrm>>3)&0x7;
176 trace(Callstack_depth+1, "run") << "subtract " << rname(arg2) << " from r/m32" << end();
177 int32_t* signed_arg1 = effective_address(modrm);
178 int32_t signed_result = *signed_arg1 - Reg[arg2].i;
179 SF = (signed_result < 0);
180 ZF = (signed_result == 0);
181 int64_t signed_full_result = static_cast<int64_t>(*signed_arg1) - Reg[arg2].i;
182 OF = (signed_result != signed_full_result);
183
184 uint32_t unsigned_arg1 = static_cast<uint32_t>(*signed_arg1);
185 uint32_t unsigned_result = unsigned_arg1 - Reg[arg2].u;
186 uint64_t unsigned_full_result = static_cast<uint64_t>(unsigned_arg1) - Reg[arg2].u;
187 CF = (unsigned_result != unsigned_full_result);
188 trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
189 *signed_arg1 = signed_result;
190 trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *signed_arg1 << end();
191 break;
192 }
193
194 :(code)
195 void test_subtract_r32_from_r32_signed_overflow() {
196 Reg[EAX].i = INT32_MIN;
197 Reg[EBX].i = INT32_MAX;
198 run(
199 "== code 0x1\n"
200
201 " 29 d8 \n"
202
203 );
204 CHECK_TRACE_CONTENTS(
205 "run: subtract EBX from r/m32\n"
206 "run: r/m32 is EAX\n"
207 "run: SF=0; ZF=0; CF=0; OF=1\n"
208 "run: storing 0x00000001\n"
209 );
210 }
211
212 void test_subtract_r32_from_r32_unsigned_overflow() {
213 Reg[EAX].i = 0;
214 Reg[EBX].i = 1;
215 run(
216 "== code 0x1\n"
217
218 " 29 d8 \n"
219
220 );
221 CHECK_TRACE_CONTENTS(
222 "run: subtract EBX from r/m32\n"
223 "run: r/m32 is EAX\n"
224 "run: SF=1; ZF=0; CF=1; OF=0\n"
225 "run: storing 0xffffffff\n"
226 );
227 }
228
229 void test_subtract_r32_from_r32_signed_and_unsigned_overflow() {
230 Reg[EAX].i = 0;
231 Reg[EBX].i = INT32_MIN;
232 run(
233 "== code 0x1\n"
234
235 " 29 d8 \n"
236
237 );
238 CHECK_TRACE_CONTENTS(
239 "run: subtract EBX from r/m32\n"
240 "run: r/m32 is EAX\n"
241 "run: SF=1; ZF=0; CF=1; OF=1\n"
242 "run: storing 0x80000000\n"
243 );
244 }
245
246
247
248 :(before "End Initialize Op Names")
249 put_new(Name, "f7", "negate/multiply/divide rm32 (with EAX and EDX if necessary) depending on subop (neg/mul/idiv)");
250
251 :(code)
252 void test_multiply_EAX_by_r32() {
253 Reg[EAX].i = 4;
254 Reg[ECX].i = 3;
255 run(
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long *///: Construct types out of their constituent fields.
void test_merge() {
run(
"container foo [\n"
" x:num\n"
" y:num\n"
"]\n"
"def main [\n"
" 1:foo <- merge 3, 4\n"
"]\n"
);
CHECK_TRACE_CONTENTS(
"mem: storing 3 in location 1\n"
"mem: storing 4 in location 2\n"
);
}
:(before "End Primitive Recipe Declarations")
MERGE,
:(before "End Primitive Recipe Numbers")
put(Recipe_ordinal, "merge", MERGE);
:(before "End Primitive Recipe Checks")
case MERGE: {
// type-checking in a separate transform below
break;
}
:(before "End Primitive Recipe Implementations")
case MERGE: {
products.resize(1);
for (int i = 0; i < SIZE(ingredients); ++i)
for (int j = 0; j < SIZE(ingredients.at(i)); ++j)
products.at(0).push_back(ingredients.at(i).at(j));
break;
}
//: type-check 'merge' to avoid interpreting numbers as addresses
:(code)
void test_merge_check() {
run(
"def main [\n"
" 1:point <- merge 3, 4\n"
"]\n"
);
CHECK_TRACE_COUNT("error", 0);
}
void test_merge_check_missing_element() {
Hide_errors = true;
run(
"def main [\n"
" 1:point <- merge 3\n"
"]\n"
);
CHECK_TRACE_CONTENTS(
"error: main: too few ingredients in '1:point <- merge 3'\n"
);
}
void test_merge_check_extra_element() {
Hide_errors = true;
run(
"def main [\n"
" 1:point <- merge 3, 4, 5\n"
"]\n"
);
CHECK_TRACE_CONTENTS(
"error: main: too many ingredients in '1:point <- merge 3, 4, 5'\n"
);
}
//: We want to avoid causing memory corruption, but other than that we want to
//: be flexible in how we construct containers of containers. It should be
//: equally easy to define a container out of primitives or intermediate
//: container fields.
void test_merge_check_recursive_containers() {
run(
"def main [\n"
" 1:point <- merge 3, 4\n"
" 1:point-number <- merge 1:point, 5\n"
"]\n"
);
CHECK_TRACE_COUNT("error", 0);
}
void test_merge_check_recursive_containers_2() {
Hide_errors = true;
run(
"def main [\n"
" 1:point <- merge 3, 4\n"
" 2:point-number <- merge 1:point\n"
"]\n"
);
CHECK_TRACE_CONTENTS(
"error: main: too few ingredients in '2:point-number <- merge 1:point'\n"
);
}
void test_merge_check_recursive_containers_3() {
run(
"def main [\n"
" 1:point-number <- merge 3, 4, 5\n"
"]\n"
);
CHECK_TRACE_COUNT("error", 0);
}
void test_merge_check_recursive_containers_4() {
Hide_errors = true;
run(
"def main [\n"
" 1:point-number <- merge 3, 4\n"
"]\n"
);
CHECK_TRACE_CONTENTS(
"error: main: too few ingredients in '1:point-number <- merge 3, 4'\n"
);
}
void test_merge_check_reflexive() {
Hide_errors = true;
run(
"def main [\n"
" 1:point <- merge 3, 4\n"
" 2:point <- merge 1:point\n"
"]\n"
);
CHECK_TRACE_COUNT("error", 0);
}
//: Since a container can be merged in several ways, we need to be able to
//: backtrack through different possibilities. Later we'll allow creating
//: exclusive containers which contain just one of rather than all of their
//: elements. That will also require backtracking capabilities. Here's the
//: state we need to maintain for backtracking:
:(before "End Types")
struct merge_check_point {
reagent container;
int container_element_index;
merge_check_point(const reagent& c, int i) :container(c), container_element_index(i) {}
};
struct merge_check_state {
stack<merge_check_point> data;
};
:(before "End Checks")
Transform.push_back(check_merge_calls); // idempotent
:(code)
void check_merge_calls(const recipe_ordinal r) {
const recipe& caller = get(Recipe, r);
trace(101, "transform") << "--- type-check merge instructions in recipe " << caller.name << end();
for (int i = 0; i < SIZE(caller.steps); ++i) {
const instruction& inst = caller.steps.at(i);
if (inst.name != "merge") continue;
if (SIZE(inst.products) != 1) {
raise << maybe(caller.name) << "'merge' should yield a single product in '" << to_original_string(inst) << "'\n" << end();
continue;
}
reagent/*copy*/ product = inst.products.at(0);
// Update product While Type-checking Merge
const type_tree* product_base_type = product.type->atom ? product.type : product.type->left;
assert(product_base_type->atom);
if (product_base_type->value == 0 || !contains_key(Type, product_base_type->value)) {
raise << maybe(caller.name) << "'merge' should yield a container in '" << to_original_string(inst) << "'\n" << end();
continue;
}
const type_info& info = get(Type, product_base_type->value);
if (info.kind != CONTAINER && info.kind != EXCLUSIVE_CONTAINER) {
raise << maybe(caller.name) << "'merge' should yield a container in '" << to_original_string(inst) << "'\n" << end();
continue;
}
check_merge_call(inst.ingredients, product, caller, inst);
}
}
void check_merge_call(const vector<reagent>& ingredients, const reagent& product, const recipe& caller, const instruction& inst) {
int ingredient_index = 0;
merge_check_state state;
state.data.push(merge_check_point(product, 0));
while (true) {
assert(!state.data.empty());
trace(102, "transform") << ingredient_index << " vs " << SIZE(ingredients) << end();
if (ingredient_index >= SIZE(ingredients)) {
raise << maybe(caller.name) << "too few ingredients in '" << to_original_string(inst) << "'\n" << end();
return;
}
reagent& container = state.data.top().container;
if (!container.type) return; // error handled elsewhere
const type_tree* top_root_type = container.type->atom ? container.type : container.type->left;
assert(top_root_type->atom);
type_info& container_info = get(Type, top_root_type->value);
switch (container_info.kind) {
case CONTAINER: {
// degenerate case: merge with the same type always succeeds
if (state.data.top().container_element_index == 0 && types_coercible(container, inst.ingredients.at(ingredient_index)))
return;
const reagent& expected_ingredient = element_type(container.type, state.data.top().container_element_index);
trace(102, "transform") << "checking container " << to_string(container) << " || " << to_string(expected_ingredient) << " vs ingredient " << ingredient_index << end();
// if the current element is the ingredient we expect, move on to the next element/ingredient
if (types_coercible(expected_ingredient, ingredients.at(ingredient_index))) {
++ingredient_index;
++state.data.top().container_element_index;
while (state.data.top().container_element_index >= SIZE(get(Type, get_base_type(state.data.top().container.type)->value).elements)) {
state.data.pop();
if (state.data.empty()) {
if (ingredient_index < SIZE(ingredients))
raise << maybe(caller.name) << "too many ingredients in '" << to_original_string(inst) << "'\n" << end();
return;
}
++state.data.top().container_element_index;
}
}
// if not, maybe it's a field of the current element
else {
// no change to ingredient_index
state.data.push(merge_check_point(expected_ingredient, 0));
}
break;
}
// End check_merge_call Special-cases
default: {
if (!types_coercible(container, ingredients.at(ingredient_index))) {
raise << maybe(caller.name) << "incorrect type of ingredient " << ingredient_index << " in '" << to_original_string(inst) << "'\n" << end();
raise << " (expected '" << debug_string(container) << "')\n" << end();
raise << " (got '" << debug_string(ingredients.at(ingredient_index)) << "')\n" << end();
return;
}
++ingredient_index;
// ++state.data.top().container_element_index; // unnecessary, but wouldn't do any harm
do {
state.data.pop();
if (state.data.empty()) {
if (ingredient_index < SIZE(ingredients))
raise << maybe(caller.name) << "too many ingredients in '" << to_original_string(inst) << "'\n" << end();
return;
}
++state.data.top().container_element_index;
} while (state.data.top().container_element_index >= SIZE(get(Type, get_base_type(state.data.top().container.type)->value).elements));
}
}
}
// never gets here
assert(false);
}
//: replaced in a later layer
//: todo: find some clean way to take this call completely out of this layer
const type_tree* get_base_type(const type_tree* t) {
return t;
}
void test_merge_check_product() {
Hide_errors = true;
run(
"def main [\n"
" 1:num <- merge 3\n"
"]\n"
);
CHECK_TRACE_CONTENTS(
"error: main: 'merge' should yield a container in '1:num <- merge 3'\n"
);
}
:(before "End Includes")
#include <stack>
using std::stack;