Skip to content

Commit 6ec411a

Browse files
committed
Compile IfFalse, IfTrue, and Jump instructions (Shopify/zjit#72)
* Compile IfFalse instruction * Add a TODO comment * Rename *s_len to num_*s * Run only gen_param() against block.params * Add a few more tests * Wrap label indexes with Label * Compile blocks in reverse post-order * Simplify a nested test * s/get_block/block/ * Return a number instead of an iterator * Clarify the allocator uses disjoint sets of registers * Use Display for Block and Insn * Compile IfTrue and Jump * Avoid resolving Param instructions * Always compile Insn::Param as basic block arguments * Remove an obsoleted variable * Change it back to use find * Use find for params too * Use Display more * Add more tests * nested if * if after if * if elsif else * loop after loop * nested loops * if in loop * loop in if
1 parent efb2ba3 commit 6ec411a

File tree

9 files changed

+562
-176
lines changed

9 files changed

+562
-176
lines changed

test/ruby/test_zjit.rb

Lines changed: 171 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -161,39 +161,191 @@ def test(a, b) = a >= b
161161
}, call_threshold: 2
162162
end
163163

164+
def test_if
165+
assert_compiles '[0, nil]', %q{
166+
def test(n)
167+
if n < 5
168+
0
169+
end
170+
end
171+
[test(3), test(7)]
172+
}
173+
end
164174

175+
def test_if_else
176+
assert_compiles '[0, 1]', %q{
177+
def test(n)
178+
if n < 5
179+
0
180+
else
181+
1
182+
end
183+
end
184+
[test(3), test(7)]
185+
}
186+
end
165187

166-
# FIXME: missing IfFalse insn
167-
#def test_if_else
168-
# assert_compiles '[0, 1]', %q{
169-
# def test(n)
170-
# if n < 5
171-
# 0
172-
# else
173-
# 1
174-
# end
175-
# end
176-
# [test(3), test(7)]
177-
# }, call_threshold: 2
178-
#end
188+
def test_if_else_params
189+
assert_compiles '[1, 20]', %q{
190+
def test(n, a, b)
191+
if n < 5
192+
a
193+
else
194+
b
195+
end
196+
end
197+
[test(3, 1, 2), test(7, 10, 20)]
198+
}
199+
end
179200

201+
def test_if_else_nested
202+
assert_compiles '[3, 8, 9, 14]', %q{
203+
def test(a, b, c, d, e)
204+
if 2 < a
205+
if a < 4
206+
b
207+
else
208+
c
209+
end
210+
else
211+
if a < 0
212+
d
213+
else
214+
e
215+
end
216+
end
217+
end
218+
[
219+
test(-1, 1, 2, 3, 4),
220+
test( 0, 5, 6, 7, 8),
221+
test( 3, 9, 10, 11, 12),
222+
test( 5, 13, 14, 15, 16),
223+
]
224+
}
225+
end
180226

227+
def test_if_else_chained
228+
assert_compiles '[12, 11, 21]', %q{
229+
def test(a)
230+
(if 2 < a then 1 else 2 end) + (if a < 4 then 10 else 20 end)
231+
end
232+
[test(0), test(3), test(5)]
233+
}
234+
end
235+
236+
def test_if_elsif_else
237+
assert_compiles '[0, 2, 1]', %q{
238+
def test(n)
239+
if n < 5
240+
0
241+
elsif 8 < n
242+
1
243+
else
244+
2
245+
end
246+
end
247+
[test(3), test(7), test(9)]
248+
}
249+
end
250+
251+
def test_ternary_operator
252+
assert_compiles '[1, 20]', %q{
253+
def test(n, a, b)
254+
n < 5 ? a : b
255+
end
256+
[test(3, 1, 2), test(7, 10, 20)]
257+
}
258+
end
181259

260+
def test_ternary_operator_nested
261+
assert_compiles '[2, 21]', %q{
262+
def test(n, a, b)
263+
(n < 5 ? a : b) + 1
264+
end
265+
[test(3, 1, 2), test(7, 10, 20)]
266+
}
267+
end
182268

183-
# FIXME: need to call twice because of call threshold 2, but
184-
# then this fails because of missing FixnumLt
185269
def test_while_loop
186270
assert_compiles '10', %q{
187-
def loop_fun(n)
271+
def test(n)
188272
i = 0
189273
while i < n
190274
i = i + 1
191275
end
192276
i
193277
end
194-
loop_fun(10)
195-
#loop_fun(10)
196-
}, call_threshold: 2
278+
test(10)
279+
}
280+
end
281+
282+
def test_while_loop_chain
283+
assert_compiles '[135, 270]', %q{
284+
def test(n)
285+
i = 0
286+
while i < n
287+
i = i + 1
288+
end
289+
while i < n * 10
290+
i = i * 3
291+
end
292+
i
293+
end
294+
[test(5), test(10)]
295+
}
296+
end
297+
298+
def test_while_loop_nested
299+
assert_compiles '[0, 4, 12]', %q{
300+
def test(n, m)
301+
i = 0
302+
while i < n
303+
j = 0
304+
while j < m
305+
j += 2
306+
end
307+
i += j
308+
end
309+
i
310+
end
311+
[test(0, 0), test(1, 3), test(10, 5)]
312+
}
313+
end
314+
315+
def test_while_loop_if_else
316+
assert_compiles '[9, -1]', %q{
317+
def test(n)
318+
i = 0
319+
while i < n
320+
if n >= 10
321+
return -1
322+
else
323+
i = i + 1
324+
end
325+
end
326+
i
327+
end
328+
[test(9), test(10)]
329+
}
330+
end
331+
332+
def test_if_while_loop
333+
assert_compiles '[9, 12]', %q{
334+
def test(n)
335+
i = 0
336+
if n < 10
337+
while i < n
338+
i += 1
339+
end
340+
else
341+
while i < n
342+
i += 3
343+
end
344+
end
345+
i
346+
end
347+
[test(9), test(10)]
348+
}
197349
end
198350

199351
private

zjit/src/asm/arm64/opnd.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22

33
/// This operand represents a register.
4-
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
4+
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
55
pub struct A64Reg
66
{
77
// Size in bits

zjit/src/asm/mod.rs

Lines changed: 83 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,26 @@ use std::collections::BTreeMap;
22
//use std::fmt;
33
use std::rc::Rc;
44
use std::cell::RefCell;
5+
use std::mem;
56
use crate::virtualmem::*;
67

78
// Lots of manual vertical alignment in there that rustfmt doesn't handle well.
89
#[rustfmt::skip]
910
pub mod x86_64;
1011
pub mod arm64;
1112

13+
/// Index to a label created by cb.new_label()
14+
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
15+
pub struct Label(pub usize);
16+
1217
/// Reference to an ASM label
1318
#[derive(Clone)]
1419
pub struct LabelRef {
1520
// Position in the code block where the label reference exists
1621
pos: usize,
1722

1823
// Label which this refers to
19-
label_idx: usize,
24+
label: Label,
2025

2126
/// The number of bytes that this label reference takes up in the memory.
2227
/// It's necessary to know this ahead of time so that when we come back to
@@ -32,9 +37,21 @@ pub struct CodeBlock {
3237
// Memory for storing the encoded instructions
3338
mem_block: Rc<RefCell<VirtualMem>>,
3439

40+
// Memory block size
41+
mem_size: usize,
42+
3543
// Current writing position
3644
write_pos: usize,
3745

46+
// Table of registered label addresses
47+
label_addrs: Vec<usize>,
48+
49+
// Table of registered label names
50+
label_names: Vec<String>,
51+
52+
// References to labels
53+
label_refs: Vec<LabelRef>,
54+
3855
// A switch for keeping comments. They take up memory.
3956
keep_comments: bool,
4057

@@ -50,9 +67,14 @@ pub struct CodeBlock {
5067
impl CodeBlock {
5168
/// Make a new CodeBlock
5269
pub fn new(mem_block: Rc<RefCell<VirtualMem>>, keep_comments: bool) -> Self {
70+
let mem_size = mem_block.borrow().virtual_region_size();
5371
Self {
5472
mem_block,
73+
mem_size,
5574
write_pos: 0,
75+
label_addrs: Vec::new(),
76+
label_names: Vec::new(),
77+
label_refs: Vec::new(),
5678
keep_comments,
5779
asm_comments: BTreeMap::new(),
5880
dropped_bytes: false,
@@ -144,21 +166,70 @@ impl CodeBlock {
144166
self.dropped_bytes
145167
}
146168

169+
/// Allocate a new label with a given name
170+
pub fn new_label(&mut self, name: String) -> Label {
171+
assert!(!name.contains(' '), "use underscores in label names, not spaces");
172+
173+
// This label doesn't have an address yet
174+
self.label_addrs.push(0);
175+
self.label_names.push(name);
176+
177+
Label(self.label_addrs.len() - 1)
178+
}
179+
180+
/// Write a label at the current address
181+
pub fn write_label(&mut self, label: Label) {
182+
self.label_addrs[label.0] = self.write_pos;
183+
}
184+
147185
// Add a label reference at the current write position
148-
pub fn label_ref(&mut self, _label_idx: usize, _num_bytes: usize, _encode: fn(&mut CodeBlock, i64, i64)) {
149-
// TODO: copy labels
186+
pub fn label_ref(&mut self, label: Label, num_bytes: usize, encode: fn(&mut CodeBlock, i64, i64)) {
187+
assert!(label.0 < self.label_addrs.len());
150188

151-
//assert!(label_idx < self.label_addrs.len());
189+
// Keep track of the reference
190+
self.label_refs.push(LabelRef { pos: self.write_pos, label, num_bytes, encode });
152191

153-
//// Keep track of the reference
154-
//self.label_refs.push(LabelRef { pos: self.write_pos, label_idx, num_bytes, encode });
192+
// Move past however many bytes the instruction takes up
193+
if self.write_pos + num_bytes < self.mem_size {
194+
self.write_pos += num_bytes;
195+
} else {
196+
self.dropped_bytes = true; // retry emitting the Insn after next_page
197+
}
198+
}
199+
200+
// Link internal label references
201+
pub fn link_labels(&mut self) {
202+
let orig_pos = self.write_pos;
203+
204+
// For each label reference
205+
for label_ref in mem::take(&mut self.label_refs) {
206+
let ref_pos = label_ref.pos;
207+
let label_idx = label_ref.label.0;
208+
assert!(ref_pos < self.mem_size);
209+
210+
let label_addr = self.label_addrs[label_idx];
211+
assert!(label_addr < self.mem_size);
212+
213+
self.write_pos = ref_pos;
214+
(label_ref.encode)(self, (ref_pos + label_ref.num_bytes) as i64, label_addr as i64);
215+
216+
// Assert that we've written the same number of bytes that we
217+
// expected to have written.
218+
assert!(self.write_pos == ref_pos + label_ref.num_bytes);
219+
}
220+
221+
self.write_pos = orig_pos;
222+
223+
// Clear the label positions and references
224+
self.label_addrs.clear();
225+
self.label_names.clear();
226+
assert!(self.label_refs.is_empty());
227+
}
155228

156-
//// Move past however many bytes the instruction takes up
157-
//if self.has_capacity(num_bytes) {
158-
// self.write_pos += num_bytes;
159-
//} else {
160-
// self.dropped_bytes = true; // retry emitting the Insn after next_page
161-
//}
229+
pub fn clear_labels(&mut self) {
230+
self.label_addrs.clear();
231+
self.label_names.clear();
232+
self.label_refs.clear();
162233
}
163234

164235
/// Make all the code in the region executable. Call this at the end of a write session.

0 commit comments

Comments
 (0)