Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/cranelift/codegen/meta/src/gen_inst.rs
1692 views
1
//! Generate CLIF instruction data (including opcodes, formats, builders, etc.).
2
3
use crate::cdsl::camel_case;
4
use crate::cdsl::formats::InstructionFormat;
5
use crate::cdsl::instructions::{AllInstructions, Instruction};
6
use crate::cdsl::operands::{Operand, OperandKindFields};
7
use crate::cdsl::typevar::{TypeSet, TypeVar};
8
use crate::unique_table::{UniqueSeqTable, UniqueTable};
9
use cranelift_codegen_shared::constant_hash;
10
use cranelift_srcgen::{Formatter, Language, Match, error, fmtln};
11
use std::fmt;
12
use std::rc::Rc;
13
14
// TypeSet indexes are encoded in 8 bits, with `0xff` reserved.
15
const TYPESET_LIMIT: usize = 0xff;
16
17
/// Generate an instruction format enumeration.
18
fn gen_formats(formats: &[Rc<InstructionFormat>], fmt: &mut Formatter) {
19
fmt.doc_comment(
20
r#"
21
An instruction format
22
23
Every opcode has a corresponding instruction format
24
which is represented by both the `InstructionFormat`
25
and the `InstructionData` enums.
26
"#,
27
);
28
fmt.line("#[derive(Copy, Clone, PartialEq, Eq, Debug)]");
29
fmt.add_block("pub enum InstructionFormat", |fmt| {
30
for format in formats {
31
fmt.doc_comment(format.to_string());
32
fmtln!(fmt, "{},", format.name);
33
}
34
});
35
fmt.empty_line();
36
37
// Emit a From<InstructionData> which also serves to verify that
38
// InstructionFormat and InstructionData are in sync.
39
fmt.add_block(
40
"impl<'a> From<&'a InstructionData> for InstructionFormat",
41
|fmt| {
42
fmt.add_block("fn from(inst: &'a InstructionData) -> Self", |fmt| {
43
let mut m = Match::new("*inst");
44
for format in formats {
45
m.arm(
46
format!("InstructionData::{}", format.name),
47
vec![".."],
48
format!("Self::{}", format.name),
49
);
50
}
51
fmt.add_match(m);
52
});
53
},
54
);
55
fmt.empty_line();
56
}
57
58
/// Generate the InstructionData enum.
59
///
60
/// Every variant must contain an `opcode` field. The size of `InstructionData` should be kept at
61
/// 16 bytes on 64-bit architectures. If more space is needed to represent an instruction, use a
62
/// `ValueList` to store the additional information out of line.
63
fn gen_instruction_data(formats: &[Rc<InstructionFormat>], fmt: &mut Formatter) {
64
fmt.line("#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]");
65
fmt.line(r#"#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]"#);
66
fmt.line("#[allow(missing_docs, reason = \"generated code\")]");
67
fmt.add_block("pub enum InstructionData", |fmt| {
68
for format in formats {
69
fmt.add_block(&format!("{}", format.name), |fmt| {
70
fmt.line("opcode: Opcode,");
71
if format.has_value_list {
72
fmt.line("args: ValueList,");
73
} else if format.num_value_operands == 1 {
74
fmt.line("arg: Value,");
75
} else if format.num_value_operands > 0 {
76
fmtln!(fmt, "args: [Value; {}],", format.num_value_operands);
77
}
78
79
match format.num_block_operands {
80
0 => (),
81
1 => fmt.line("destination: ir::BlockCall,"),
82
2 => fmtln!(
83
fmt,
84
"blocks: [ir::BlockCall; {}],",
85
format.num_block_operands
86
),
87
n => panic!("Too many block operands in instruction: {n}"),
88
}
89
90
match format.num_raw_block_operands {
91
0 => (),
92
1 => fmt.line("block: ir::Block,"),
93
n => panic!("Too many block operands in instruction: {n}"),
94
}
95
96
for field in &format.imm_fields {
97
fmtln!(fmt, "{}: {},", field.member, field.kind.rust_type);
98
}
99
});
100
fmtln!(fmt, ",");
101
}
102
});
103
}
104
105
fn gen_arguments_method(formats: &[Rc<InstructionFormat>], fmt: &mut Formatter, is_mut: bool) {
106
let (method, mut_, rslice, as_slice) = if is_mut {
107
(
108
"arguments_mut",
109
"mut ",
110
"core::slice::from_mut",
111
"as_mut_slice",
112
)
113
} else {
114
("arguments", "", "core::slice::from_ref", "as_slice")
115
};
116
117
fmt.add_block(&format!(
118
"pub fn {method}<'a>(&'a {mut_}self, pool: &'a {mut_}ir::ValueListPool) -> &'a {mut_}[Value]"),
119
120
|fmt| {
121
let mut m = Match::new("*self");
122
for format in formats {
123
let name = format!("Self::{}", format.name);
124
125
// Formats with a value list put all of their arguments in the list. We don't split
126
// them up, just return it all as variable arguments. (I expect the distinction to go
127
// away).
128
if format.has_value_list {
129
m.arm(
130
name,
131
vec![format!("ref {}args", mut_), "..".to_string()],
132
format!("args.{as_slice}(pool)"),
133
);
134
continue;
135
}
136
137
// Fixed args.
138
let mut fields = Vec::new();
139
let arg = if format.num_value_operands == 0 {
140
format!("&{mut_}[]")
141
} else if format.num_value_operands == 1 {
142
fields.push(format!("ref {mut_}arg"));
143
format!("{rslice}(arg)")
144
} else {
145
let arg = format!("args_arity{}", format.num_value_operands);
146
fields.push(format!("args: ref {mut_}{arg}"));
147
arg
148
};
149
fields.push("..".into());
150
151
m.arm(name, fields, arg);
152
}
153
fmt.add_match(m);
154
});
155
}
156
157
/// Generate the boring parts of the InstructionData implementation.
158
///
159
/// These methods in `impl InstructionData` can be generated automatically from the instruction
160
/// formats:
161
///
162
/// - `pub fn opcode(&self) -> Opcode`
163
/// - `pub fn arguments(&self, &pool) -> &[Value]`
164
/// - `pub fn arguments_mut(&mut self, &pool) -> &mut [Value]`
165
/// - `pub fn eq(&self, &other: Self, &pool) -> bool`
166
/// - `pub fn hash<H: Hasher>(&self, state: &mut H, &pool)`
167
fn gen_instruction_data_impl(formats: &[Rc<InstructionFormat>], fmt: &mut Formatter) {
168
fmt.add_block("impl InstructionData", |fmt| {
169
fmt.doc_comment("Get the opcode of this instruction.");
170
fmt.add_block("pub fn opcode(&self) -> Opcode",|fmt| {
171
let mut m = Match::new("*self");
172
for format in formats {
173
m.arm(format!("Self::{}", format.name), vec!["opcode", ".."],
174
"opcode".to_string());
175
}
176
fmt.add_match(m);
177
});
178
fmt.empty_line();
179
180
fmt.doc_comment("Get the controlling type variable operand.");
181
fmt.add_block("pub fn typevar_operand(&self, pool: &ir::ValueListPool) -> Option<Value>",|fmt| {
182
let mut m = Match::new("*self");
183
for format in formats {
184
let name = format!("Self::{}", format.name);
185
if format.typevar_operand.is_none() {
186
m.arm(name, vec![".."], "None".to_string());
187
} else if format.has_value_list {
188
// We keep all arguments in a value list.
189
m.arm(name, vec!["ref args", ".."], format!("args.get({}, pool)", format.typevar_operand.unwrap()));
190
} else if format.num_value_operands == 1 {
191
m.arm(name, vec!["arg", ".."], "Some(arg)".to_string());
192
} else {
193
// We have multiple value operands and an array `args`.
194
// Which `args` index to use?
195
let args = format!("args_arity{}", format.num_value_operands);
196
m.arm(name, vec![format!("args: ref {}", args), "..".to_string()],
197
format!("Some({}[{}])", args, format.typevar_operand.unwrap()));
198
}
199
}
200
fmt.add_match(m);
201
});
202
fmt.empty_line();
203
204
fmt.doc_comment("Get the value arguments to this instruction.");
205
gen_arguments_method(formats, fmt, false);
206
fmt.empty_line();
207
208
fmt.doc_comment(r#"Get mutable references to the value arguments to this
209
instruction."#);
210
gen_arguments_method(formats, fmt, true);
211
fmt.empty_line();
212
213
fmt.doc_comment(r#"
214
Compare two `InstructionData` for equality.
215
216
This operation requires a reference to a `ValueListPool` to
217
determine if the contents of any `ValueLists` are equal.
218
219
This operation takes a closure that is allowed to map each
220
argument value to some other value before the instructions
221
are compared. This allows various forms of canonicalization.
222
"#);
223
fmt.add_block("pub fn eq(&self, other: &Self, pool: &ir::ValueListPool) -> bool", |fmt| {
224
fmt.add_block("if ::core::mem::discriminant(self) != ::core::mem::discriminant(other)", |fmt| {
225
fmt.line("return false;");
226
});
227
228
fmt.add_block("match (self, other)",|fmt| {
229
for format in formats {
230
let name = format!("&Self::{}", format.name);
231
let mut members = vec!["opcode"];
232
233
let args_eq = if format.has_value_list {
234
members.push("args");
235
Some("args1.as_slice(pool).iter().zip(args2.as_slice(pool).iter()).all(|(a, b)| a == b)")
236
} else if format.num_value_operands == 1 {
237
members.push("arg");
238
Some("arg1 == arg2")
239
} else if format.num_value_operands > 0 {
240
members.push("args");
241
Some("args1.iter().zip(args2.iter()).all(|(a, b)| a == b)")
242
} else {
243
None
244
};
245
246
let blocks_eq = match format.num_block_operands {
247
0 => None,
248
1 => {
249
members.push("destination");
250
Some("destination1 == destination2")
251
},
252
_ => {
253
members.push("blocks");
254
Some("blocks1.iter().zip(blocks2.iter()).all(|(a, b)| a.block(pool) == b.block(pool))")
255
}
256
};
257
258
let raw_blocks_eq = match format.num_raw_block_operands {
259
0 => None,
260
1 => {
261
members.push("block");
262
Some("block1 == block2")
263
}
264
_ => unreachable!("Not a valid format"),
265
};
266
267
for field in &format.imm_fields {
268
members.push(field.member);
269
}
270
271
let pat1 = members.iter().map(|x| format!("{x}: ref {x}1")).collect::<Vec<_>>().join(", ");
272
let pat2 = members.iter().map(|x| format!("{x}: ref {x}2")).collect::<Vec<_>>().join(", ");
273
fmt.add_block(&format!("({name} {{ {pat1} }}, {name} {{ {pat2} }}) => "), |fmt| {
274
fmt.line("opcode1 == opcode2");
275
for field in &format.imm_fields {
276
fmtln!(fmt, "&& {}1 == {}2", field.member, field.member);
277
}
278
if let Some(args_eq) = args_eq {
279
fmtln!(fmt, "&& {}", args_eq);
280
}
281
if let Some(blocks_eq) = blocks_eq {
282
fmtln!(fmt, "&& {}", blocks_eq);
283
}
284
if let Some(raw_blocks_eq) = raw_blocks_eq {
285
fmtln!(fmt, "&& {}", raw_blocks_eq);
286
}
287
});
288
}
289
fmt.line("_ => unreachable!()");
290
});
291
});
292
fmt.empty_line();
293
294
fmt.doc_comment(r#"
295
Hash an `InstructionData`.
296
297
This operation requires a reference to a `ValueListPool` to
298
hash the contents of any `ValueLists`.
299
300
This operation takes a closure that is allowed to map each
301
argument value to some other value before it is hashed. This
302
allows various forms of canonicalization.
303
"#);
304
fmt.add_block("pub fn hash<H: ::core::hash::Hasher>(&self, state: &mut H, pool: &ir::ValueListPool)",|fmt| {
305
fmt.add_block("match *self",|fmt| {
306
for format in formats {
307
let name = format!("Self::{}", format.name);
308
let mut members = vec!["opcode"];
309
310
let (args, len) = if format.has_value_list {
311
members.push("ref args");
312
(Some("args.as_slice(pool)"), "args.len(pool)")
313
} else if format.num_value_operands == 1 {
314
members.push("ref arg");
315
(Some("std::slice::from_ref(arg)"), "1")
316
} else if format.num_value_operands > 0 {
317
members.push("ref args");
318
(Some("args"), "args.len()")
319
} else {
320
(None, "0")
321
};
322
323
let blocks = match format.num_block_operands {
324
0 => None,
325
1 => {
326
members.push("ref destination");
327
Some(("std::slice::from_ref(destination)", "1"))
328
}
329
_ => {
330
members.push("ref blocks");
331
Some(("blocks", "blocks.len()"))
332
}
333
};
334
335
let raw_block = match format.num_raw_block_operands {
336
0 => None,
337
1 => {
338
members.push("block");
339
Some("block")
340
}
341
_ => panic!("Too many raw block operands"),
342
};
343
344
for field in &format.imm_fields {
345
members.push(field.member);
346
}
347
let members = members.join(", ");
348
349
fmt.add_block(&format!("{name}{{{members}}} => "), |fmt| {
350
fmt.line("::core::hash::Hash::hash( &::core::mem::discriminant(self), state);");
351
fmt.line("::core::hash::Hash::hash(&opcode, state);");
352
for field in &format.imm_fields {
353
fmtln!(fmt, "::core::hash::Hash::hash(&{}, state);", field.member);
354
}
355
fmtln!(fmt, "::core::hash::Hash::hash(&{}, state);", len);
356
if let Some(args) = args {
357
fmt.add_block(&format!("for &arg in {args}"), |fmt| {
358
fmtln!(fmt, "::core::hash::Hash::hash(&arg, state);");
359
});
360
}
361
362
if let Some((blocks, len)) = blocks {
363
fmtln!(fmt, "::core::hash::Hash::hash(&{len}, state);");
364
fmt.add_block(&format!("for &block in {blocks}"), |fmt| {
365
fmtln!(fmt, "::core::hash::Hash::hash(&block.block(pool), state);");
366
fmt.add_block("for arg in block.args(pool)", |fmt| {
367
fmtln!(fmt, "::core::hash::Hash::hash(&arg, state);");
368
});
369
});
370
}
371
372
if let Some(raw_block) = raw_block {
373
fmtln!(fmt, "::core::hash::Hash::hash(&{raw_block}, state);");
374
}
375
});
376
}
377
});
378
});
379
380
fmt.empty_line();
381
382
fmt.doc_comment(r#"
383
Deep-clone an `InstructionData`, including any referenced lists.
384
385
This operation requires a reference to a `ValueListPool` to
386
clone the `ValueLists`.
387
"#);
388
fmt.add_block("pub fn deep_clone(&self, pool: &mut ir::ValueListPool) -> Self",|fmt| {
389
fmt.add_block("match *self",|fmt| {
390
for format in formats {
391
let name = format!("Self::{}", format.name);
392
let mut members = vec!["opcode"];
393
394
if format.has_value_list {
395
members.push("ref args");
396
} else if format.num_value_operands == 1 {
397
members.push("arg");
398
} else if format.num_value_operands > 0 {
399
members.push("args");
400
}
401
402
match format.num_block_operands {
403
0 => {}
404
1 => {
405
members.push("destination");
406
}
407
_ => {
408
members.push("blocks");
409
}
410
};
411
412
match format.num_raw_block_operands {
413
0 => {}
414
1 => {
415
members.push("block");
416
}
417
_ => panic!("Too many raw-block operands to format"),
418
}
419
420
for field in &format.imm_fields {
421
members.push(field.member);
422
}
423
let members = members.join(", ");
424
425
fmt.add_block(&format!("{name}{{{members}}} => "),|fmt| {
426
fmt.add_block(&format!("Self::{}", format.name), |fmt| {
427
fmtln!(fmt, "opcode,");
428
429
if format.has_value_list {
430
fmtln!(fmt, "args: args.deep_clone(pool),");
431
} else if format.num_value_operands == 1 {
432
fmtln!(fmt, "arg,");
433
} else if format.num_value_operands > 0 {
434
fmtln!(fmt, "args,");
435
}
436
437
match format.num_block_operands {
438
0 => {}
439
1 => {
440
fmtln!(fmt, "destination: destination.deep_clone(pool),");
441
}
442
2 => {
443
fmtln!(fmt, "blocks: [blocks[0].deep_clone(pool), blocks[1].deep_clone(pool)],");
444
}
445
_ => panic!("Too many block targets in instruction"),
446
}
447
448
match format.num_raw_block_operands {
449
0 => {}
450
1 => {
451
fmtln!(fmt, "block,");
452
}
453
_ => panic!("Too many raw-block operands in instruction"),
454
}
455
456
for field in &format.imm_fields {
457
fmtln!(fmt, "{},", field.member);
458
}
459
});
460
});
461
}
462
});
463
});
464
fmt.doc_comment(r#"
465
Map some functions, described by the given `InstructionMapper`, over each of the
466
entities within this instruction, producing a new `InstructionData`.
467
"#);
468
fmt.add_block("pub fn map(&self, mut mapper: impl crate::ir::instructions::InstructionMapper) -> Self", |fmt| {
469
fmt.add_block("match *self",|fmt| {
470
for format in formats {
471
let name = format!("Self::{}", format.name);
472
let mut members = vec!["opcode"];
473
474
if format.has_value_list {
475
members.push("args");
476
} else if format.num_value_operands == 1 {
477
members.push("arg");
478
} else if format.num_value_operands > 0 {
479
members.push("args");
480
}
481
482
match format.num_block_operands {
483
0 => {}
484
1 => {
485
members.push("destination");
486
}
487
_ => {
488
members.push("blocks");
489
}
490
};
491
492
match format.num_raw_block_operands {
493
0 => {}
494
1 => {
495
members.push("block");
496
}
497
_ => panic!("Too many raw-block operands"),
498
}
499
500
for field in &format.imm_fields {
501
members.push(field.member);
502
}
503
let members = members.join(", ");
504
505
fmt.add_block(&format!("{name}{{{members}}} => "), |fmt| {
506
fmt.add_block(&format!("Self::{}", format.name), |fmt| {
507
fmtln!(fmt, "opcode,");
508
509
if format.has_value_list {
510
fmtln!(fmt, "args: mapper.map_value_list(args),");
511
} else if format.num_value_operands == 1 {
512
fmtln!(fmt, "arg: mapper.map_value(arg),");
513
} else if format.num_value_operands > 0 {
514
let maps = (0..format.num_value_operands)
515
.map(|i| format!("mapper.map_value(args[{i}])"))
516
.collect::<Box<[_]>>()
517
.join(", ");
518
fmtln!(fmt, "args: [{maps}],");
519
}
520
521
match format.num_block_operands {
522
0 => {}
523
1 => {
524
fmtln!(fmt, "destination: mapper.map_block_call(destination),");
525
}
526
2 => {
527
fmtln!(fmt, "blocks: [mapper.map_block_call(blocks[0]), mapper.map_block_call(blocks[1])],");
528
}
529
_ => panic!("Too many block targets in instruction"),
530
}
531
532
match format.num_raw_block_operands {
533
0 => {}
534
1 => {
535
fmtln!(fmt, "block: mapper.map_block(block),");
536
}
537
_ => panic!("Too many raw block arguments in instruction"),
538
}
539
540
for field in &format.imm_fields {
541
let member = field.member;
542
match &field.kind.fields {
543
OperandKindFields::EntityRef => {
544
let mut kind = heck::ToSnakeCase::to_snake_case(
545
field
546
.kind
547
.rust_type
548
.split("::")
549
.last()
550
.unwrap_or(field.kind.rust_type),
551
);
552
if kind == "block" {
553
kind.push_str("_call");
554
}
555
fmtln!(fmt, "{member}: mapper.map_{kind}({member}),");
556
}
557
OperandKindFields::VariableArgs => {
558
fmtln!(fmt, "{member}: mapper.map_value_list({member}),");
559
}
560
OperandKindFields::ImmValue |
561
OperandKindFields::ImmEnum(_) |
562
OperandKindFields::TypeVar(_) => fmtln!(fmt, "{member},"),
563
}
564
}
565
});
566
});
567
}
568
});
569
});
570
});
571
}
572
573
fn gen_bool_accessor<T: Fn(&Instruction) -> bool>(
574
all_inst: &AllInstructions,
575
get_attr: T,
576
name: &'static str,
577
doc: &'static str,
578
fmt: &mut Formatter,
579
) {
580
fmt.doc_comment(doc);
581
fmt.add_block(&format!("pub fn {name}(self) -> bool"), |fmt| {
582
let mut m = Match::new("self");
583
for inst in all_inst.iter() {
584
if get_attr(inst) {
585
m.arm_no_fields(format!("Self::{}", inst.camel_name), "true");
586
}
587
}
588
m.arm_no_fields("_", "false");
589
fmt.add_match(m);
590
});
591
fmt.empty_line();
592
}
593
594
fn gen_opcodes(all_inst: &AllInstructions, fmt: &mut Formatter) {
595
fmt.doc_comment(
596
r#"
597
An instruction opcode.
598
599
All instructions from all supported ISAs are present.
600
"#,
601
);
602
fmt.line("#[repr(u8)]");
603
fmt.line("#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]");
604
fmt.line(
605
r#"#[cfg_attr(
606
feature = "enable-serde",
607
derive(serde_derive::Serialize, serde_derive::Deserialize)
608
)]"#,
609
);
610
611
// We explicitly set the discriminant of the first variant to 1, which allows us to take
612
// advantage of the NonZero optimization, meaning that wrapping enums can use the 0
613
// discriminant instead of increasing the size of the whole type, and so the size of
614
// Option<Opcode> is the same as Opcode's.
615
fmt.add_block("pub enum Opcode", |fmt| {
616
let mut is_first_opcode = true;
617
for inst in all_inst.iter() {
618
fmt.doc_comment(format!("`{}`. ({})", inst, inst.format.name));
619
620
// Document polymorphism.
621
if let Some(poly) = &inst.polymorphic_info {
622
if poly.use_typevar_operand {
623
let op_num = inst.value_opnums[inst.format.typevar_operand.unwrap()];
624
fmt.doc_comment(format!(
625
"Type inferred from `{}`.",
626
inst.operands_in[op_num].name
627
));
628
}
629
}
630
631
// Enum variant itself.
632
if is_first_opcode {
633
fmtln!(fmt, "{} = 1,", inst.camel_name);
634
is_first_opcode = false;
635
} else {
636
fmtln!(fmt, "{},", inst.camel_name)
637
}
638
}
639
});
640
fmt.empty_line();
641
642
fmt.add_block("impl Opcode", |fmt| {
643
gen_bool_accessor(
644
all_inst,
645
|inst| inst.is_terminator,
646
"is_terminator",
647
"True for instructions that terminate the block",
648
fmt,
649
);
650
gen_bool_accessor(
651
all_inst,
652
|inst| inst.is_branch,
653
"is_branch",
654
"True for all branch or jump instructions.",
655
fmt,
656
);
657
gen_bool_accessor(
658
all_inst,
659
|inst| inst.is_call,
660
"is_call",
661
"Is this a call instruction?",
662
fmt,
663
);
664
gen_bool_accessor(
665
all_inst,
666
|inst| inst.is_return,
667
"is_return",
668
"Is this a return instruction?",
669
fmt,
670
);
671
gen_bool_accessor(
672
all_inst,
673
|inst| inst.can_load,
674
"can_load",
675
"Can this instruction read from memory?",
676
fmt,
677
);
678
gen_bool_accessor(
679
all_inst,
680
|inst| inst.can_store,
681
"can_store",
682
"Can this instruction write to memory?",
683
fmt,
684
);
685
gen_bool_accessor(
686
all_inst,
687
|inst| inst.can_trap,
688
"can_trap",
689
"Can this instruction cause a trap?",
690
fmt,
691
);
692
gen_bool_accessor(
693
all_inst,
694
|inst| inst.other_side_effects,
695
"other_side_effects",
696
"Does this instruction have other side effects besides can_* flags?",
697
fmt,
698
);
699
gen_bool_accessor(
700
all_inst,
701
|inst| inst.side_effects_idempotent,
702
"side_effects_idempotent",
703
"Despite having side effects, is this instruction okay to GVN?",
704
fmt,
705
);
706
707
// Generate an opcode list, for iterating over all known opcodes.
708
fmt.doc_comment("All cranelift opcodes.");
709
fmt.add_block("pub fn all() -> &'static [Opcode]", |fmt| {
710
fmt.line("return &[");
711
for inst in all_inst {
712
fmt.indent(|fmt| {
713
fmtln!(fmt, "Opcode::{},", inst.camel_name);
714
});
715
}
716
fmt.line("];");
717
});
718
fmt.empty_line();
719
});
720
fmt.empty_line();
721
722
// Generate a private opcode_format table.
723
fmtln!(
724
fmt,
725
"const OPCODE_FORMAT: [InstructionFormat; {}] = [",
726
all_inst.len()
727
);
728
fmt.indent(|fmt| {
729
for inst in all_inst.iter() {
730
fmtln!(
731
fmt,
732
"InstructionFormat::{}, // {}",
733
inst.format.name,
734
inst.name
735
);
736
}
737
});
738
fmtln!(fmt, "];");
739
fmt.empty_line();
740
741
// Generate a private opcode_name function.
742
fmt.add_block("fn opcode_name(opc: Opcode) -> &\'static str", |fmt| {
743
let mut m = Match::new("opc");
744
for inst in all_inst.iter() {
745
m.arm_no_fields(
746
format!("Opcode::{}", inst.camel_name),
747
format!("\"{}\"", inst.name),
748
);
749
}
750
fmt.add_match(m);
751
});
752
fmt.empty_line();
753
754
// Generate an opcode hash table for looking up opcodes by name.
755
let hash_table =
756
crate::constant_hash::generate_table(all_inst.iter(), all_inst.len(), |inst| {
757
constant_hash::simple_hash(&inst.name)
758
});
759
fmtln!(
760
fmt,
761
"const OPCODE_HASH_TABLE: [Option<Opcode>; {}] = [",
762
hash_table.len()
763
);
764
fmt.indent(|fmt| {
765
for i in hash_table {
766
match i {
767
Some(i) => fmtln!(fmt, "Some(Opcode::{}),", i.camel_name),
768
None => fmtln!(fmt, "None,"),
769
}
770
}
771
});
772
fmtln!(fmt, "];");
773
fmt.empty_line();
774
}
775
776
/// Get the value type constraint for an SSA value operand, where
777
/// `ctrl_typevar` is the controlling type variable.
778
///
779
/// Each operand constraint is represented as a string, one of:
780
/// - `Concrete(vt)`, where `vt` is a value type name.
781
/// - `Free(idx)` where `idx` is an index into `type_sets`.
782
/// - `Same`, `Lane`, `AsTruthy` for controlling typevar-derived constraints.
783
fn get_constraint<'entries, 'table>(
784
operand: &'entries Operand,
785
ctrl_typevar: Option<&TypeVar>,
786
type_sets: &'table mut UniqueTable<'entries, TypeSet>,
787
) -> String {
788
assert!(operand.is_value());
789
let type_var = operand.type_var().unwrap();
790
791
if let Some(typ) = type_var.singleton_type() {
792
return format!("Concrete({})", typ.rust_name());
793
}
794
795
if let Some(free_typevar) = type_var.free_typevar() {
796
if ctrl_typevar.is_some() && free_typevar != *ctrl_typevar.unwrap() {
797
assert!(type_var.base.is_none());
798
return format!("Free({})", type_sets.add(type_var.get_raw_typeset()));
799
}
800
}
801
802
if let Some(base) = &type_var.base {
803
assert!(base.type_var == *ctrl_typevar.unwrap());
804
return camel_case(base.derived_func.name());
805
}
806
807
assert!(type_var == ctrl_typevar.unwrap());
808
"Same".into()
809
}
810
811
fn gen_bitset<'a, T: IntoIterator<Item = &'a u16>>(
812
iterable: T,
813
name: &'static str,
814
field_size: u8,
815
fmt: &mut Formatter,
816
) {
817
let bits = iterable.into_iter().fold(0, |acc, x| {
818
assert!(x.is_power_of_two());
819
assert!(u32::from(*x) < (1 << u32::from(field_size)));
820
acc | x
821
});
822
fmtln!(fmt, "{}: ScalarBitSet::<u{}>({}),", name, field_size, bits);
823
}
824
825
fn iterable_to_string<I: fmt::Display, T: IntoIterator<Item = I>>(iterable: T) -> String {
826
let elems = iterable
827
.into_iter()
828
.map(|x| x.to_string())
829
.collect::<Vec<_>>()
830
.join(", ");
831
format!("{{{elems}}}")
832
}
833
834
fn typeset_to_string(ts: &TypeSet) -> String {
835
let mut result = format!("TypeSet(lanes={}", iterable_to_string(&ts.lanes));
836
if !ts.ints.is_empty() {
837
result += &format!(", ints={}", iterable_to_string(&ts.ints));
838
}
839
if !ts.floats.is_empty() {
840
result += &format!(", floats={}", iterable_to_string(&ts.floats));
841
}
842
result += ")";
843
result
844
}
845
846
/// Generate the table of ValueTypeSets described by type_sets.
847
pub(crate) fn gen_typesets_table(type_sets: &UniqueTable<TypeSet>, fmt: &mut Formatter) {
848
if type_sets.len() == 0 {
849
return;
850
}
851
852
fmt.comment("Table of value type sets.");
853
assert!(type_sets.len() <= TYPESET_LIMIT, "Too many type sets!");
854
fmtln!(
855
fmt,
856
"const TYPE_SETS: [ir::instructions::ValueTypeSet; {}] = [",
857
type_sets.len()
858
);
859
fmt.indent(|fmt| {
860
for ts in type_sets.iter() {
861
fmt.add_block("ir::instructions::ValueTypeSet", |fmt| {
862
fmt.comment(typeset_to_string(ts));
863
gen_bitset(&ts.lanes, "lanes", 16, fmt);
864
gen_bitset(&ts.dynamic_lanes, "dynamic_lanes", 16, fmt);
865
gen_bitset(&ts.ints, "ints", 8, fmt);
866
gen_bitset(&ts.floats, "floats", 8, fmt);
867
});
868
fmt.line(",");
869
}
870
});
871
fmtln!(fmt, "];");
872
}
873
874
/// Generate value type constraints for all instructions.
875
/// - Emit a compact constant table of ValueTypeSet objects.
876
/// - Emit a compact constant table of OperandConstraint objects.
877
/// - Emit an opcode-indexed table of instruction constraints.
878
fn gen_type_constraints(all_inst: &AllInstructions, fmt: &mut Formatter) {
879
// Table of TypeSet instances.
880
let mut type_sets = UniqueTable::new();
881
882
// Table of operand constraint sequences (as tuples). Each operand
883
// constraint is represented as a string, one of:
884
// - `Concrete(vt)`, where `vt` is a value type name.
885
// - `Free(idx)` where `idx` is an index into `type_sets`.
886
// - `Same`, `Lane`, `AsTruthy` for controlling typevar-derived constraints.
887
let mut operand_seqs = UniqueSeqTable::new();
888
889
// Preload table with constraints for typical binops.
890
operand_seqs.add(&vec!["Same".to_string(); 3]);
891
892
fmt.comment("Table of opcode constraints.");
893
fmtln!(
894
fmt,
895
"const OPCODE_CONSTRAINTS: [OpcodeConstraints; {}] = [",
896
all_inst.len()
897
);
898
fmt.indent(|fmt| {
899
for inst in all_inst.iter() {
900
let (ctrl_typevar, ctrl_typeset) = if let Some(poly) = &inst.polymorphic_info {
901
let index = type_sets.add(poly.ctrl_typevar.get_raw_typeset());
902
(Some(&poly.ctrl_typevar), index)
903
} else {
904
(None, TYPESET_LIMIT)
905
};
906
907
// Collect constraints for the value results, not including `variable_args` results
908
// which are always special cased.
909
let mut constraints = Vec::new();
910
for &index in &inst.value_results {
911
constraints.push(get_constraint(&inst.operands_out[index], ctrl_typevar, &mut type_sets));
912
}
913
for &index in &inst.value_opnums {
914
constraints.push(get_constraint(&inst.operands_in[index], ctrl_typevar, &mut type_sets));
915
}
916
917
let constraint_offset = operand_seqs.add(&constraints);
918
919
let fixed_results = inst.value_results.len();
920
let fixed_values = inst.value_opnums.len();
921
922
// Can the controlling type variable be inferred from the designated operand?
923
let use_typevar_operand = if let Some(poly) = &inst.polymorphic_info {
924
poly.use_typevar_operand
925
} else {
926
false
927
};
928
929
// Can the controlling type variable be inferred from the result?
930
let use_result = fixed_results > 0 && inst.operands_out[inst.value_results[0]].type_var() == ctrl_typevar;
931
932
// Are we required to use the designated operand instead of the result?
933
let requires_typevar_operand = use_typevar_operand && !use_result;
934
935
fmt.comment(
936
format!("{}: fixed_results={}, use_typevar_operand={}, requires_typevar_operand={}, fixed_values={}",
937
inst.camel_name,
938
fixed_results,
939
use_typevar_operand,
940
requires_typevar_operand,
941
fixed_values)
942
);
943
fmt.comment(format!("Constraints=[{}]", constraints
944
.iter()
945
.map(|x| format!("'{x}'"))
946
.collect::<Vec<_>>()
947
.join(", ")));
948
if let Some(poly) = &inst.polymorphic_info {
949
fmt.comment(format!("Polymorphic over {}", typeset_to_string(poly.ctrl_typevar.get_raw_typeset())));
950
}
951
952
// Compute the bit field encoding, c.f. instructions.rs.
953
assert!(fixed_results < 8 && fixed_values < 8, "Bit field encoding too tight");
954
let mut flags = fixed_results; // 3 bits
955
if use_typevar_operand {
956
flags |= 1<<3; // 4th bit
957
}
958
if requires_typevar_operand {
959
flags |= 1<<4; // 5th bit
960
}
961
flags |= fixed_values << 5; // 6th bit and more
962
963
fmt.add_block("OpcodeConstraints",|fmt| {
964
fmtln!(fmt, "flags: {:#04x},", flags);
965
fmtln!(fmt, "typeset_offset: {},", ctrl_typeset);
966
fmtln!(fmt, "constraint_offset: {},", constraint_offset);
967
});
968
fmt.line(",");
969
}
970
});
971
fmtln!(fmt, "];");
972
fmt.empty_line();
973
974
gen_typesets_table(&type_sets, fmt);
975
fmt.empty_line();
976
977
fmt.comment("Table of operand constraint sequences.");
978
fmtln!(
979
fmt,
980
"const OPERAND_CONSTRAINTS: [OperandConstraint; {}] = [",
981
operand_seqs.len()
982
);
983
fmt.indent(|fmt| {
984
for constraint in operand_seqs.iter() {
985
fmtln!(fmt, "OperandConstraint::{},", constraint);
986
}
987
});
988
fmtln!(fmt, "];");
989
}
990
991
/// Emit member initializers for an instruction format.
992
fn gen_member_inits(format: &InstructionFormat, fmt: &mut Formatter) {
993
// Immediate operands.
994
// We have local variables with the same names as the members.
995
for f in &format.imm_fields {
996
fmtln!(fmt, "{},", f.member);
997
}
998
999
// Value operands.
1000
if format.has_value_list {
1001
fmt.line("args,");
1002
} else if format.num_value_operands == 1 {
1003
fmt.line("arg: arg0,");
1004
} else if format.num_value_operands > 1 {
1005
let mut args = Vec::new();
1006
for i in 0..format.num_value_operands {
1007
args.push(format!("arg{i}"));
1008
}
1009
fmtln!(fmt, "args: [{}],", args.join(", "));
1010
}
1011
1012
// Block operands
1013
match format.num_block_operands {
1014
0 => (),
1015
1 => fmt.line("destination: block0"),
1016
n => {
1017
let mut blocks = Vec::new();
1018
for i in 0..n {
1019
blocks.push(format!("block{i}"));
1020
}
1021
fmtln!(fmt, "blocks: [{}],", blocks.join(", "));
1022
}
1023
}
1024
1025
// Raw block operands.
1026
match format.num_raw_block_operands {
1027
0 => (),
1028
1 => fmt.line("block: block0,"),
1029
_ => panic!("Too many raw block arguments"),
1030
}
1031
}
1032
1033
/// Emit a method for creating and inserting an instruction format.
1034
///
1035
/// All instruction formats take an `opcode` argument and a `ctrl_typevar` argument for deducing
1036
/// the result types.
1037
fn gen_format_constructor(format: &InstructionFormat, fmt: &mut Formatter) {
1038
// Construct method arguments.
1039
let mut args = vec![
1040
"self".to_string(),
1041
"opcode: Opcode".into(),
1042
"ctrl_typevar: Type".into(),
1043
];
1044
1045
// Raw block operands.
1046
args.extend((0..format.num_raw_block_operands).map(|i| format!("block{i}: ir::Block")));
1047
1048
// Normal operand arguments. Start with the immediate operands.
1049
for f in &format.imm_fields {
1050
args.push(format!("{}: {}", f.member, f.kind.rust_type));
1051
}
1052
1053
// Then the block operands.
1054
args.extend((0..format.num_block_operands).map(|i| format!("block{i}: ir::BlockCall")));
1055
1056
// Then the value operands.
1057
if format.has_value_list {
1058
// Take all value arguments as a finished value list. The value lists
1059
// are created by the individual instruction constructors.
1060
args.push("args: ir::ValueList".into());
1061
} else {
1062
// Take a fixed number of value operands.
1063
for i in 0..format.num_value_operands {
1064
args.push(format!("arg{i}: Value"));
1065
}
1066
}
1067
1068
let proto = format!(
1069
"{}({}) -> (Inst, &'f mut ir::DataFlowGraph)",
1070
format.name,
1071
args.join(", ")
1072
);
1073
1074
let imms_need_masking = format
1075
.imm_fields
1076
.iter()
1077
.any(|f| f.kind.rust_type == "ir::immediates::Imm64");
1078
1079
fmt.doc_comment(format.to_string());
1080
fmt.line("#[allow(non_snake_case, reason = \"generated code\")]");
1081
fmt.add_block(&format!("fn {proto}"), |fmt| {
1082
// Generate the instruction data.
1083
fmt.add_block(&format!(
1084
"let{} data = ir::InstructionData::{}",
1085
if imms_need_masking { " mut" } else { "" },
1086
format.name
1087
), |fmt| {
1088
fmt.line("opcode,");
1089
gen_member_inits(format, fmt);
1090
});
1091
fmtln!(fmt, ";");
1092
1093
if imms_need_masking {
1094
fmtln!(fmt, "data.mask_immediates(ctrl_typevar);");
1095
}
1096
1097
// Assert that this opcode belongs to this format
1098
fmtln!(fmt, "debug_assert_eq!(opcode.format(), InstructionFormat::from(&data), \"Wrong InstructionFormat for Opcode: {{opcode}}\");");
1099
1100
fmt.line("self.build(data, ctrl_typevar)");
1101
});
1102
}
1103
1104
/// Emit a method for generating the instruction `inst`.
1105
///
1106
/// The method will create and insert an instruction, then return the result values, or the
1107
/// instruction reference itself for instructions that don't have results.
1108
fn gen_inst_builder(inst: &Instruction, format: &InstructionFormat, fmt: &mut Formatter) {
1109
// Construct method arguments.
1110
let mut args = vec![String::new()];
1111
1112
let mut args_doc = Vec::new();
1113
let mut rets_doc = Vec::new();
1114
1115
// The controlling type variable will be inferred from the input values if
1116
// possible. Otherwise, it is the first method argument.
1117
if let Some(poly) = &inst.polymorphic_info {
1118
if !poly.use_typevar_operand {
1119
args.push(format!("{}: crate::ir::Type", poly.ctrl_typevar.name));
1120
args_doc.push(format!(
1121
"- {} (controlling type variable): {}",
1122
poly.ctrl_typevar.name, poly.ctrl_typevar.doc
1123
));
1124
}
1125
}
1126
1127
let mut tmpl_types = Vec::new();
1128
let mut into_args = Vec::new();
1129
let mut block_args = Vec::new();
1130
let mut lifetime_param = None;
1131
for op in &inst.operands_in {
1132
if op.kind.is_block() {
1133
args.push(format!("{}_label: {}", op.name, "ir::Block"));
1134
args_doc.push(format!(
1135
"- {}_label: {}",
1136
op.name, "Destination basic block"
1137
));
1138
1139
let lifetime = *lifetime_param.get_or_insert_with(|| {
1140
tmpl_types.insert(0, "'a".to_string());
1141
"'a"
1142
});
1143
args.push(format!(
1144
"{}_args: impl IntoIterator<Item = &{} BlockArg>",
1145
op.name, lifetime,
1146
));
1147
args_doc.push(format!("- {}_args: {}", op.name, "Block arguments"));
1148
1149
block_args.push(op);
1150
} else if op.kind.is_raw_block() {
1151
args.push("block: ir::Block".into());
1152
args_doc.push("- block: raw basic block".into());
1153
} else {
1154
let t = if op.is_immediate() {
1155
let t = format!("T{}", tmpl_types.len() + 1);
1156
tmpl_types.push(format!("{}: Into<{}>", t, op.kind.rust_type));
1157
into_args.push(op.name);
1158
t
1159
} else {
1160
op.kind.rust_type.to_string()
1161
};
1162
args.push(format!("{}: {}", op.name, t));
1163
args_doc.push(format!("- {}: {}", op.name, op.doc()));
1164
}
1165
}
1166
1167
// We need to mutate `self` if this instruction accepts a value list, or will construct
1168
// BlockCall values.
1169
if format.has_value_list || !block_args.is_empty() {
1170
args[0].push_str("mut self");
1171
} else {
1172
args[0].push_str("self");
1173
}
1174
1175
for op in &inst.operands_out {
1176
rets_doc.push(format!("- {}: {}", op.name, op.doc()));
1177
}
1178
1179
let rtype = match inst.value_results.len() {
1180
0 => "Inst".into(),
1181
1 => "Value".into(),
1182
_ => format!("({})", vec!["Value"; inst.value_results.len()].join(", ")),
1183
};
1184
1185
let tmpl = if !tmpl_types.is_empty() {
1186
format!("<{}>", tmpl_types.join(", "))
1187
} else {
1188
"".into()
1189
};
1190
1191
let proto = format!(
1192
"{}{}({}) -> {}",
1193
inst.snake_name(),
1194
tmpl,
1195
args.join(", "),
1196
rtype
1197
);
1198
1199
fmt.doc_comment(&inst.doc);
1200
if !args_doc.is_empty() {
1201
fmt.line("///");
1202
fmt.doc_comment("Inputs:");
1203
fmt.line("///");
1204
for doc_line in args_doc {
1205
fmt.doc_comment(doc_line);
1206
}
1207
}
1208
if !rets_doc.is_empty() {
1209
fmt.line("///");
1210
fmt.doc_comment("Outputs:");
1211
fmt.line("///");
1212
for doc_line in rets_doc {
1213
fmt.doc_comment(doc_line);
1214
}
1215
}
1216
1217
fmt.line("#[allow(non_snake_case, reason = \"generated code\")]");
1218
fmt.add_block(&format!("fn {proto}"), |fmt| {
1219
// Convert all of the `Into<>` arguments.
1220
for arg in into_args {
1221
fmtln!(fmt, "let {} = {}.into();", arg, arg);
1222
}
1223
1224
// Convert block references
1225
for op in block_args {
1226
fmtln!(
1227
fmt,
1228
"let {0} = self.data_flow_graph_mut().block_call({0}_label, {0}_args);",
1229
op.name
1230
);
1231
}
1232
1233
// Arguments for instruction constructor.
1234
let first_arg = format!("Opcode::{}", inst.camel_name);
1235
let mut args = vec![first_arg.as_str()];
1236
if let Some(poly) = &inst.polymorphic_info {
1237
if poly.use_typevar_operand {
1238
// Infer the controlling type variable from the input operands.
1239
let op_num = inst.value_opnums[format.typevar_operand.unwrap()];
1240
fmtln!(
1241
fmt,
1242
"let ctrl_typevar = self.data_flow_graph().value_type({});",
1243
inst.operands_in[op_num].name
1244
);
1245
1246
// The format constructor will resolve the result types from the type var.
1247
args.push("ctrl_typevar");
1248
} else {
1249
// This was an explicit method argument.
1250
args.push(&poly.ctrl_typevar.name);
1251
}
1252
} else {
1253
// No controlling type variable needed.
1254
args.push("types::INVALID");
1255
}
1256
1257
// Now add all of the immediate operands to the constructor arguments.
1258
for &op_num in &inst.imm_opnums {
1259
args.push(inst.operands_in[op_num].name);
1260
}
1261
1262
// Finally, the value operands.
1263
if format.has_value_list {
1264
// We need to build a value list with all the arguments.
1265
fmt.line("let mut vlist = ir::ValueList::default();");
1266
args.push("vlist");
1267
fmt.line("{");
1268
fmt.indent(|fmt| {
1269
fmt.line("let pool = &mut self.data_flow_graph_mut().value_lists;");
1270
for op in &inst.operands_in {
1271
if op.is_value() {
1272
fmtln!(fmt, "vlist.push({}, pool);", op.name);
1273
} else if op.is_varargs() {
1274
fmtln!(fmt, "vlist.extend({}.iter().cloned(), pool);", op.name);
1275
}
1276
}
1277
});
1278
fmt.line("}");
1279
} else {
1280
// With no value list, we're guaranteed to just have a set of fixed value operands.
1281
for &op_num in &inst.value_opnums {
1282
args.push(inst.operands_in[op_num].name);
1283
}
1284
}
1285
1286
// Call to the format constructor,
1287
let fcall = format!("self.{}({})", format.name, args.join(", "));
1288
1289
fmtln!(fmt, "let (inst, dfg) = {};", fcall);
1290
fmtln!(
1291
fmt,
1292
"crate::trace!(\"inserted {{inst:?}}: {{}}\", dfg.display_inst(inst));"
1293
);
1294
1295
if inst.value_results.is_empty() {
1296
fmtln!(fmt, "inst");
1297
return;
1298
}
1299
1300
if inst.value_results.len() == 1 {
1301
fmt.line("dfg.first_result(inst)");
1302
} else {
1303
fmtln!(
1304
fmt,
1305
"let results = &dfg.inst_results(inst)[0..{}];",
1306
inst.value_results.len()
1307
);
1308
fmtln!(
1309
fmt,
1310
"({})",
1311
inst.value_results
1312
.iter()
1313
.enumerate()
1314
.map(|(i, _)| format!("results[{i}]"))
1315
.collect::<Vec<_>>()
1316
.join(", ")
1317
);
1318
}
1319
});
1320
}
1321
1322
/// Generate a Builder trait with methods for all instructions.
1323
fn gen_builder(
1324
instructions: &AllInstructions,
1325
formats: &[Rc<InstructionFormat>],
1326
fmt: &mut Formatter,
1327
) {
1328
fmt.doc_comment(
1329
r#"
1330
Convenience methods for building instructions.
1331
1332
The `InstBuilder` trait has one method per instruction opcode for
1333
conveniently constructing the instruction with minimum arguments.
1334
Polymorphic instructions infer their result types from the input
1335
arguments when possible. In some cases, an explicit `ctrl_typevar`
1336
argument is required.
1337
1338
The opcode methods return the new instruction's result values, or
1339
the `Inst` itself for instructions that don't have any results.
1340
1341
There is also a method per instruction format. These methods all
1342
return an `Inst`.
1343
1344
When an address to a load or store is specified, its integer
1345
size is required to be equal to the platform's pointer width.
1346
"#,
1347
);
1348
fmt.add_block("pub trait InstBuilder<'f>: InstBuilderBase<'f>", |fmt| {
1349
for inst in instructions.iter() {
1350
gen_inst_builder(inst, &inst.format, fmt);
1351
fmt.empty_line();
1352
}
1353
for (i, format) in formats.iter().enumerate() {
1354
gen_format_constructor(format, fmt);
1355
if i + 1 != formats.len() {
1356
fmt.empty_line();
1357
}
1358
}
1359
});
1360
}
1361
1362
pub(crate) fn generate(
1363
formats: &[Rc<InstructionFormat>],
1364
all_inst: &AllInstructions,
1365
opcode_filename: &str,
1366
inst_builder_filename: &str,
1367
out_dir: &std::path::Path,
1368
) -> Result<(), error::Error> {
1369
// Opcodes.
1370
let mut fmt = Formatter::new(Language::Rust);
1371
gen_formats(&formats, &mut fmt);
1372
gen_instruction_data(&formats, &mut fmt);
1373
fmt.empty_line();
1374
gen_instruction_data_impl(&formats, &mut fmt);
1375
fmt.empty_line();
1376
gen_opcodes(all_inst, &mut fmt);
1377
fmt.empty_line();
1378
gen_type_constraints(all_inst, &mut fmt);
1379
fmt.write(opcode_filename, out_dir)?;
1380
1381
// Instruction builder.
1382
let mut fmt = Formatter::new(Language::Rust);
1383
gen_builder(all_inst, &formats, &mut fmt);
1384
fmt.write(inst_builder_filename, out_dir)?;
1385
1386
Ok(())
1387
}
1388
1389