https://github.com/akkartik/mu/blob/master/003trace.cc
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 :(before "End Types")
71 struct trace_line {
72 int depth;
73 string label;
74 string contents;
75 trace_line(string l, string c) :depth(0), label(l), contents(c) {}
76 trace_line(int d, string l, string c) :depth(d), label(l), contents(c) {}
77 };
78
79
80
81 :(before "End Commandline Options(*arg)")
82 else if (is_equal(*arg, "--trace")) {
83 Save_trace = true;
84 }
85 :(before "End Commandline Parsing")
86 if (Save_trace) {
87 cerr << "initializing trace\n";
88 Trace_stream = new trace_stream;
89 }
90 :(code)
91 void cleanup_main() {
92 if (!Trace_stream) return;
93 if (Save_trace)
94 Trace_stream->save();
95 delete Trace_stream;
96 Trace_stream = NULL;
97 }
98 :(before "End One-time Setup")
99 atexit(cleanup_main);
100
101 :(before "End Types")
102
103
104
105 const int Max_depth = 9999;
106 const int Error_depth = 0;
107 const int App_depth = 2;
108
109 struct trace_stream {
110 vector<trace_line> past_lines;
111
112 ostringstream* curr_stream;
113 string curr_label;
114 int curr_depth;
115 int callstack_depth;
116 int collect_depth;
117 ofstream null_stream;
118 trace_stream() :curr_stream(NULL), curr_depth(Max_depth), callstack_depth(0), collect_depth(Max_depth) {}
119 ~trace_stream() { if (curr_stream) delete curr_stream; }
120
121 ostream& stream(string label) {
122 return stream(Max_depth, label);
123 }
124
125 ostream& stream(int depth, string label) {
126 if (depth > collect_depth) return null_stream;
127 curr_stream = new ostringstream;
128 curr_label = label;
129 curr_depth = depth;
130 return *curr_stream;
131 }
132
133 void save() {
134 cerr << "saving trace to 'last_run'\n";
135 ofstream fout("last_run");
136 fout << readable_contents("");
137 fout.close();
138 }
139
140
141 void newline();
142
143 string readable_contents(string label);
144 };
145
146 :(code)
147 void trace_stream::newline() {
148 if (!curr_stream) return;
149 string curr_contents = curr_stream->str();
150 if (!curr_contents.empty()) {
151 past_lines.push_back(trace_line(curr_depth, trim(curr_label), curr_contents));
152 if ((!Hide_errors && curr_label == "error")
153 || Dump_trace
154 || (!Dump_label.empty() && curr_label == Dump_label))
155 cerr << curr_label << ": " << curr_contents << '\n';
156 }
157 delete curr_stream;
158 curr_stream = NULL;
159 curr_label.clear();
160 curr_depth = Max_depth;
161 }
162
163 string trace_stream::readable_contents(string label) {
164 ostringstream output;
165 label = trim(label);
166 for (vector<trace_line>::iterator p = past_lines.begin(); p != past_lines.end(); ++p)
167 if (label.empty() || label == p->label)
168 output << std::setw(4) << p->depth << ' ' << p->label << ": " << p->contents << '\n';
169 return output.str();
170 }
171
172 :(before "End Globals")
173 trace_stream* Trace_stream = NULL;
174 int Trace_errors = 0;
175
176 :(before "End Globals")
177 bool Hide_errors = false;
178 bool Dump_trace = false;
179 string Dump_label = "";
180 :(before "End Reset")
181 Hide_errors = false;
182 Dump_trace = false;
183 Dump_label = "";
184
185 :(before "End Includes")
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202 :(before "End One-time Setup")
203 atexit(scroll_to_bottom_and_close_console);
204 :(code)
205 void scroll_to_bottom_and_close_console() {
206 if (!tb_is_active()) return;
207
208 tb_set_cursor(tb_width()-1, tb_height()-1);
209 cout << "\r\n";
210 tb_shutdown();
211 }
212
213
214
215 :(before "End Test Teardown")
216 if (Passed && !Hide_errors && trace_contains_errors()) {
217 Passed = false;
218 }
219 :(code)
220 bool trace_contains_errors() {
221 return Trace_errors > 0 || trace_count("error") > 0;
222 }
223
224 :(before "End Types")
225 struct end {};
226 :(code)
227 ostream& operator<<(ostream& os, end ) {
228 if (Trace_stream) Trace_stream->newline();
229 return os;
230 }
231
232 :(before "End Globals")
233 bool Save_trace = false;
234
235
236 :(before "End Types")
237 struct lease_tracer {
238 lease_tracer();
239 ~lease_tracer();
240 };
241 :(code)
242 lease_tracer::lease_tracer() { Trace_stream = new trace_stream; }
243 lease_tracer::~lease_tracer() {
244 if (Save_trace) Trace_stream->save();
245 delete Trace_stream, Trace_stream = NULL;
246 }
247 :(before "End Includes")
248
249 :(before "End Test Setup")
250 START_TRACING_UNTIL_END_OF_SCOPE
251
252 :(before "End Includes")
253
254
255
256
257 if (Passed && trace_contains_errors()) { \
258 cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): unexpected errors\n"; \
259 DUMP("error"); \
260 Passed = false; \
261 return; \
262 }
263
264
265 if (Passed && trace_count(label) != (count)) { \
266 cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): trace_count of " << label << " should be " << count << '\n'; \
267 cerr << " got " << trace_count(label) << '\n'; \
268 DUMP(label); \
269 Passed = false; \
270 return; \
271 }
272
273
274
275 :(code)
276 bool check_trace_contents(string FUNCTION, string FILE, int LINE, string expected) {
277 if (!Passed) return false;
278 if (!Trace_stream) return false;
279 vector<string> expected_lines = split(expected, "^D");
280 int curr_expected_line = 0;
281 while<# new data structure: a slice is an open interval of addresses [start, end)
# that includes 'start' but not 'end'
== code
# instruction effective address register displacement immediate
# . op subop mod rm32 base index scale r32
# . 1-3 bytes 3 bits 2 bits 3 bits 3 bits 3 bits 2 bits 2 bits 0/1/2/4 bytes 0/1/2/4 bytes
slice-empty?: # s: (addr slice) -> result/eax: boolean
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# . save registers
51/push-ecx
# ecx = s
8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 1/r32/ecx 8/disp8 . # copy *(ebp+8) to ecx
# if (s->start >= s->end) return true
# . eax = s->start
8b/copy 0/mod/indirect 1/rm32/ecx . . . 0/r32/eax . . # copy *ecx to eax
# . if (eax >= s->end) return true
3b/compare 1/mod/*+disp8 1/rm32/ecx . . . 0/r32/eax 4/disp8 . # compare eax with *(ecx+4)
b8/copy-to-eax 1/imm32/true
73/jump-if-addr>= $slice-empty?:end/disp8
b8/copy-to-eax 0/imm32/false
$slice-empty?:end:
# . restore registers
59/pop-to-ecx
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
test-slice-empty-true:
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# var slice/ecx: slice = {34, 34}
68/push 34/imm32/end
68/push 34/imm32/start
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# slice-empty?(slice)
# . . push args
51/push-ecx
# . . call
e8/call slice-empty?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 1, msg)
# . . push args
68/push "F - test-slice-empty-true"/imm32
68/push 1/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
test-slice-empty-false:
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# var slice/ecx: slice = {32, 34}
68/push 34/imm32/end
68/push 32/imm32/start
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# slice-empty?(slice)
# . . push args
51/push-ecx
# . . call
e8/call slice-empty?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 0, msg)
# . . push args
68/push "F - test-slice-empty-false"/imm32
68/push 0/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
test-slice-empty-if-start-greater-than-end:
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# var slice/ecx: slice = {34, 32}
68/push 32/imm32/end
68/push 34/imm32/start
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# slice-empty?(slice)
# . . push args
51/push-ecx
# . . call
e8/call slice-empty?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 1, msg)
# . . push args
68/push "F - test-slice-empty-if-start-greater-than-end"/imm32
68/push 1/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
slice-equal?: # s: (addr slice), p: (addr array byte) -> result/eax: boolean
# pseudocode:
# if (p == 0) return (s == 0)
# currs = s->start
# maxs = s->end
# if (maxs - currs != p->size) return false
# currp = p->data
# while currs < maxs
# if (*currs != *currp) return false
# ++currs
# ++currp
# return true
#
# registers:
# currs: edx
# maxs: esi
# currp: ebx
# *currs: eax
# *currp: ecx
#
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# . save registers
51/push-ecx
52/push-edx
53/push-ebx
56/push-esi
# esi = s
8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 6/r32/esi 8/disp8 . # copy *(ebp+8) to esi
# var currs/edx: (addr byte) = s->start
8b/copy 0/mod/indirect 6/rm32/esi . . . 2/r32/edx . . # copy *esi to edx
# var maxs/esi: (addr byte) = s->end
8b/copy 1/mod/*+disp8 6/rm32/esi . . . 6/r32/esi 4/disp8 . # copy *(esi+4) to esi
# var ssize/eax: int = maxs - currs
89/copy 3/mod/direct 0/rm32/eax . . . 6/r32/esi . . # copy esi to eax
29/subtract 3/mod/direct 0/rm32/eax . . . 2/r32/edx . . # subtract edx from eax
# ebx = p
8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 3/r32/ebx 0xc/disp8 . # copy *(ebp+12) to ebx
# if (p != 0) goto next check
81 7/subop/compare 3/mod/direct 3/rm32/ebx . . . . . 0/imm32 # compare ebx
75/jump-if-!= $slice-equal?:nonnull-string/disp8
$slice-equal?:null-string:
# return s->start == s->end
3d/compare-eax-and 0/imm32
74/jump-if-= $slice-equal?:true/disp8
eb/jump $slice-equal?:false/disp8
$slice-equal?:nonnull-string:
# if (ssize != p->size) return false
39/compare 0/mod/indirect 3/rm32/ebx . . . 0/r32/eax . . # compare *ebx and eax
75/jump-if-!= $slice-equal?:false/disp8
# var currp/ebx: (addr byte) = p->data
81 0/subop/add 3/mod/direct 3/rm32/ebx . . . . . 4/imm32 # add to ebx
# var c1/eax: byte = 0
31/xor 3/mod/direct 0/rm32/eax . . . 0/r32/eax . . # clear eax
# var c2/ecx: byte = 0
31/xor 3/mod/direct 1/rm32/ecx . . . 1/r32/ecx . . # clear ecx
$slice-equal?:loop:
# if (currs >= maxs) return true
39/compare 3/mod/direct 2/rm32/edx . . . 6/r32/esi . . # compare edx with esi
73/jump-if-addr>= $slice-equal?:true/disp8
# c1 = *currp
8a/copy-byte 0/mod/indirect 3/rm32/ebx . . . 0/r32/AL . . # copy byte at *ebx to AL
# c2 = *currs
8a/copy-byte 0/mod/indirect 2/rm32/edx . . . 1/r32/CL . . # copy byte at *edx to CL
# if (c1 != c2) return false
39/compare 3/mod/direct 0/rm32/eax . . . 1/r32/ecx . . # compare eax and ecx
75/jump-if-!= $slice-equal?:false/disp8
# ++currp
43/increment-ebx
# ++currs
42/increment-edx
eb/jump $slice-equal?:loop/disp8
$slice-equal?:false:
b8/copy-to-eax 0/imm32
eb/jump $slice-equal?:end/disp8
$slice-equal?:true:
b8/copy-to-eax 1/imm32
$slice-equal?:end:
# . restore registers
5e/pop-to-esi
5b/pop-to-ebx
5a/pop-to-edx
59/pop-to-ecx
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
test-slice-equal:
# - slice-equal?(slice("Abc"), "Abc") == 1
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# (eax..ecx) = "Abc"
b8/copy-to-eax "Abc"/imm32
8b/copy 0/mod/indirect 0/rm32/eax . . . 1/r32/ecx . . # copy *eax to ecx
8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/eax 1/index/ecx . 1/r32/ecx 4/disp8 . # copy eax+ecx+4 to ecx
05/add-to-eax 4/imm32
# var slice/ecx: slice = {eax, ecx}
51/push-ecx
50/push-eax
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# eax = slice-equal?(ecx, "Abc")
# . . push args
68/push "Abc"/imm32
51/push-ecx
# . . call
e8/call slice-equal?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
# check-ints-equal(eax, 1, msg)
# . . push args
68/push "F - test-slice-equal"/imm32
68/push 1/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
test-slice-equal-false:
# - slice-equal?(slice("bcd"), "Abc") == 0
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# (eax..ecx) = "bcd"
b8/copy-to-eax "bcd"/imm32
8b/copy 0/mod/indirect 0/rm32/eax . . . 1/r32/ecx . . # copy *eax to ecx
8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/eax 1/index/ecx . 1/r32/ecx 4/disp8 . # copy eax+ecx+4 to ecx
05/add-to-eax 4/imm32
# var slice/ecx: slice = {eax, ecx}
51/push-ecx
50/push-eax
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# eax = slice-equal?(ecx, "Abc")
# . . push args
68/push "Abc"/imm32
51/push-ecx
# . . call
e8/call slice-equal?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
# check-ints-equal(eax, 0, msg)
# . . push args
68/push "F - test-slice-equal-false"/imm32
68/push 0/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
test-slice-equal-too-long:
# - slice-equal?(slice("Abcd"), "Abc") == 0
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# (eax..ecx) = "Abcd"
b8/copy-to-eax "Abcd"/imm32
8b/copy 0/mod/indirect 0/rm32/eax . . . 1/r32/ecx . . # copy *eax to ecx
8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/eax 1/index/ecx . 1/r32/ecx 4/disp8 . # copy eax+ecx+4 to ecx
05/add-to-eax 4/imm32
# var slice/ecx: slice = {eax, ecx}
51/push-ecx
50/push-eax
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# eax = slice-equal?(ecx, "Abc")
# . . push args
68/push "Abc"/imm32
51/push-ecx
# . . call
e8/call slice-equal?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
# check-ints-equal(eax, 0, msg)
# . . push args
68/push "F - test-slice-equal-too-long"/imm32
68/push 0/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
test-slice-equal-too-short:
# - slice-equal?(slice("A"), "Abc") == 0
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# (eax..ecx) = "A"
b8/copy-to-eax "A"/imm32
8b/copy 0/mod/indirect 0/rm32/eax . . . 1/r32/ecx . . # copy *eax to ecx
8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/eax 1/index/ecx . 1/r32/ecx 4/disp8 . # copy eax+ecx+4 to ecx
05/add-to-eax 4/imm32
# var slice/ecx: slice = {eax, ecx}
51/push-ecx
50/push-eax
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# eax = slice-equal?(ecx, "Abc")
# . . push args
68/push "Abc"/imm32
51/push-ecx
# . . call
e8/call slice-equal?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
# check-ints-equal(eax, 0, msg)
# . . push args
68/push "F - test-slice-equal-too-short"/imm32
68/push 0/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
test-slice-equal-empty:
# - slice-equal?(slice(""), "Abc") == 0
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# var slice/ecx: slice
68/push 0/imm32/end
68/push 0/imm32/start
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# eax = slice-equal?(ecx, "Abc")
# . . push args
68/push "Abc"/imm32
51/push-ecx
# . . call
e8/call slice-equal?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
# check-ints-equal(eax, 0, msg)
# . . push args
68/push "F - test-slice-equal-empty"/imm32
68/push 0/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
test-slice-equal-with-empty:
# - slice-equal?(slice("Ab"), "") == 0
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# (eax..ecx) = "Ab"
b8/copy-to-eax "Ab"/imm32
8b/copy 0/mod/indirect 0/rm32/eax . . . 1/r32/ecx . . # copy *eax to ecx
8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/eax 1/index/ecx . 1/r32/ecx 4/disp8 . # copy eax+ecx+4 to ecx
05/add-to-eax 4/imm32
# var slice/ecx: slice = {eax, ecx}
51/push-ecx
50/push-eax
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# eax = slice-equal?(ecx, "")
# . . push args
68/push ""/imm32
51/push-ecx
# . . call
e8/call slice-equal?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
# check-ints-equal(eax, 0, msg)
# . . push args
68/push "F - test-slice-equal-with-empty"/imm32
68/push 0/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
test-slice-equal-empty-with-empty:
# - slice-equal?(slice(""), "") == 1
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# var slice/ecx: slice
68/push 0/imm32/end
68/push 0/imm32/start
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# eax = slice-equal?(ecx, "")
# . . push args
68/push ""/imm32
51/push-ecx
# . . call
e8/call slice-equal?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/i