]>
Commit | Line | Data |
---|---|---|
e9174d1e SL |
1 | //! See docs in build/expr/mod.rs |
2 | ||
9fa01778 XL |
3 | use crate::build::expr::category::{Category, RvalueFunc}; |
4 | use crate::build::{BlockAnd, BlockAndExtension, BlockFrame, Builder}; | |
5 | use crate::hair::*; | |
f9f354fc | 6 | use rustc_ast::ast::InlineAsmOptions; |
60c5eb7d | 7 | use rustc_data_structures::fx::FxHashMap; |
dfeec247 | 8 | use rustc_hir as hir; |
ba9703b0 XL |
9 | use rustc_middle::mir::*; |
10 | use rustc_middle::ty::{self, CanonicalUserTypeAnnotation}; | |
dfeec247 | 11 | use rustc_span::symbol::sym; |
e9174d1e | 12 | |
83c7162d | 13 | use rustc_target::spec::abi::Abi; |
cc61c64b | 14 | |
dc9dc135 | 15 | impl<'a, 'tcx> Builder<'a, 'tcx> { |
e9174d1e SL |
16 | /// Compile `expr`, storing the result into `destination`, which |
17 | /// is assumed to be uninitialized. | |
dfeec247 | 18 | crate fn into_expr( |
b7449926 | 19 | &mut self, |
ba9703b0 | 20 | destination: Place<'tcx>, |
b7449926 XL |
21 | mut block: BasicBlock, |
22 | expr: Expr<'tcx>, | |
23 | ) -> BlockAnd<()> { | |
dfeec247 | 24 | debug!("into_expr(destination={:?}, block={:?}, expr={:?})", destination, block, expr); |
e9174d1e SL |
25 | |
26 | // since we frequently have to reference `self` from within a | |
27 | // closure, where `self` would be shadowed, it's easier to | |
28 | // just use the name `this` uniformly | |
29 | let this = self; | |
30 | let expr_span = expr.span; | |
3157f602 | 31 | let source_info = this.source_info(expr_span); |
e9174d1e | 32 | |
0bf4aa26 XL |
33 | let expr_is_block_or_scope = match expr.kind { |
34 | ExprKind::Block { .. } => true, | |
35 | ExprKind::Scope { .. } => true, | |
36 | _ => false, | |
37 | }; | |
38 | ||
39 | if !expr_is_block_or_scope { | |
40 | this.block_context.push(BlockFrame::SubExpr); | |
41 | } | |
42 | ||
43 | let block_and = match expr.kind { | |
dfeec247 | 44 | ExprKind::Scope { region_scope, lint_level, value } => { |
ea8adc8c | 45 | let region_scope = (region_scope, source_info); |
dfeec247 | 46 | this.in_scope(region_scope, lint_level, |this| this.into(destination, block, value)) |
e9174d1e SL |
47 | } |
48 | ExprKind::Block { body: ast_block } => { | |
cc61c64b | 49 | this.ast_block(destination, block, ast_block, source_info) |
e9174d1e | 50 | } |
9fa01778 XL |
51 | ExprKind::Match { scrutinee, arms } => { |
52 | this.match_expr(destination, expr_span, block, scrutinee, arms) | |
e9174d1e | 53 | } |
5bcae85e SL |
54 | ExprKind::NeverToAny { source } => { |
55 | let source = this.hir.mirror(source); | |
56 | let is_call = match source.kind { | |
f9f354fc | 57 | ExprKind::Call { .. } | ExprKind::InlineAsm { .. } => true, |
5bcae85e SL |
58 | _ => false, |
59 | }; | |
60 | ||
60c5eb7d XL |
61 | // (#66975) Source could be a const of type `!`, so has to |
62 | // exist in the generated MIR. | |
dfeec247 | 63 | unpack!(block = this.as_temp(block, this.local_scope(), source, Mutability::Mut,)); |
5bcae85e SL |
64 | |
65 | // This is an optimization. If the expression was a call then we already have an | |
66 | // unreachable block. Don't bother to terminate it and create a new one. | |
67 | if is_call { | |
68 | block.unit() | |
69 | } else { | |
dfeec247 | 70 | this.cfg.terminate(block, source_info, TerminatorKind::Unreachable); |
5bcae85e SL |
71 | let end_block = this.cfg.start_new_block(); |
72 | end_block.unit() | |
73 | } | |
74 | } | |
e9174d1e SL |
75 | ExprKind::LogicalOp { op, lhs, rhs } => { |
76 | // And: | |
77 | // | |
416331ca XL |
78 | // [block: If(lhs)] -true-> [else_block: If(rhs)] -true-> [true_block] |
79 | // | | (false) | |
80 | // +----------false-----------+------------------> [false_block] | |
e9174d1e SL |
81 | // |
82 | // Or: | |
83 | // | |
416331ca XL |
84 | // [block: If(lhs)] -false-> [else_block: If(rhs)] -true-> [true_block] |
85 | // | (true) | (false) | |
86 | // [true_block] [false_block] | |
e9174d1e | 87 | |
416331ca XL |
88 | let (true_block, false_block, mut else_block, join_block) = ( |
89 | this.cfg.start_new_block(), | |
b7449926 XL |
90 | this.cfg.start_new_block(), |
91 | this.cfg.start_new_block(), | |
92 | this.cfg.start_new_block(), | |
93 | ); | |
e9174d1e | 94 | |
8bb4bdeb | 95 | let lhs = unpack!(block = this.as_local_operand(block, lhs)); |
e9174d1e | 96 | let blocks = match op { |
416331ca XL |
97 | LogicalOp::And => (else_block, false_block), |
98 | LogicalOp::Or => (true_block, else_block), | |
e9174d1e | 99 | }; |
8bb4bdeb XL |
100 | let term = TerminatorKind::if_(this.hir.tcx(), lhs, blocks.0, blocks.1); |
101 | this.cfg.terminate(block, source_info, term); | |
e9174d1e | 102 | |
416331ca XL |
103 | let rhs = unpack!(else_block = this.as_local_operand(else_block, rhs)); |
104 | let term = TerminatorKind::if_(this.hir.tcx(), rhs, true_block, false_block); | |
105 | this.cfg.terminate(else_block, source_info, term); | |
106 | ||
e9174d1e | 107 | this.cfg.push_assign_constant( |
416331ca | 108 | true_block, |
b7449926 XL |
109 | source_info, |
110 | destination, | |
dfeec247 | 111 | Constant { span: expr_span, user_ty: None, literal: this.hir.true_literal() }, |
b7449926 | 112 | ); |
416331ca XL |
113 | |
114 | this.cfg.push_assign_constant( | |
115 | false_block, | |
b7449926 | 116 | source_info, |
416331ca | 117 | destination, |
dfeec247 | 118 | Constant { span: expr_span, user_ty: None, literal: this.hir.false_literal() }, |
b7449926 | 119 | ); |
e9174d1e | 120 | |
dfeec247 XL |
121 | // Link up both branches: |
122 | this.cfg.goto(true_block, source_info, join_block); | |
123 | this.cfg.goto(false_block, source_info, join_block); | |
e9174d1e SL |
124 | join_block.unit() |
125 | } | |
416331ca | 126 | ExprKind::Loop { body } => { |
2c00a5a8 XL |
127 | // [block] |
128 | // | | |
129 | // [loop_block] -> [body_block] -/eval. body/-> [body_block_end] | |
130 | // | ^ | | |
131 | // false link | | | |
132 | // | +-----------------------------------------+ | |
133 | // +-> [diverge_cleanup] | |
134 | // The false link is required to make sure borrowck considers unwinds through the | |
135 | // body, even when the exact code in the body cannot unwind | |
e9174d1e SL |
136 | |
137 | let loop_block = this.cfg.start_new_block(); | |
138 | let exit_block = this.cfg.start_new_block(); | |
139 | ||
dfeec247 XL |
140 | // Start the loop. |
141 | this.cfg.goto(block, source_info, loop_block); | |
e9174d1e | 142 | |
ba9703b0 XL |
143 | this.in_breakable_scope(Some(loop_block), exit_block, destination, move |this| { |
144 | // conduct the test, if necessary | |
145 | let body_block = this.cfg.start_new_block(); | |
146 | let diverge_cleanup = this.diverge_cleanup(); | |
147 | this.cfg.terminate( | |
148 | loop_block, | |
149 | source_info, | |
150 | TerminatorKind::FalseUnwind { | |
151 | real_target: body_block, | |
152 | unwind: Some(diverge_cleanup), | |
153 | }, | |
154 | ); | |
155 | ||
156 | // The “return” value of the loop body must always be an unit. We therefore | |
157 | // introduce a unit temporary as the destination for the loop body. | |
158 | let tmp = this.get_unit_temp(); | |
159 | // Execute the body, branching back to the test. | |
160 | let body_block_end = unpack!(this.into(tmp, body_block, body)); | |
161 | this.cfg.goto(body_block_end, source_info, loop_block); | |
162 | }); | |
7453a54e | 163 | exit_block.unit() |
e9174d1e | 164 | } |
f035d41b | 165 | ExprKind::Call { ty, fun, args, from_hir_call, fn_span } => { |
e74abb32 | 166 | let intrinsic = match ty.kind { |
b7449926 | 167 | ty::FnDef(def_id, _) => { |
041b39d2 | 168 | let f = ty.fn_sig(this.hir.tcx()); |
b7449926 | 169 | if f.abi() == Abi::RustIntrinsic || f.abi() == Abi::PlatformIntrinsic { |
60c5eb7d | 170 | Some(this.hir.tcx().item_name(def_id)) |
041b39d2 XL |
171 | } else { |
172 | None | |
173 | } | |
9cc50fc6 | 174 | } |
b7449926 | 175 | _ => None, |
cc61c64b | 176 | }; |
cc61c64b | 177 | let fun = unpack!(block = this.as_local_operand(block, fun)); |
60c5eb7d | 178 | if let Some(sym::move_val_init) = intrinsic { |
cc61c64b XL |
179 | // `move_val_init` has "magic" semantics - the second argument is |
180 | // always evaluated "directly" into the first one. | |
181 | ||
182 | let mut args = args.into_iter(); | |
183 | let ptr = args.next().expect("0 arguments to `move_val_init`"); | |
184 | let val = args.next().expect("1 argument to `move_val_init`"); | |
185 | assert!(args.next().is_none(), ">2 arguments to `move_val_init`"); | |
186 | ||
ea8adc8c XL |
187 | let ptr = this.hir.mirror(ptr); |
188 | let ptr_ty = ptr.ty; | |
189 | // Create an *internal* temp for the pointer, so that unsafety | |
190 | // checking won't complain about the raw pointer assignment. | |
f9f354fc XL |
191 | let ptr_temp = this.local_decls.push(LocalDecl::with_source_info( |
192 | ptr_ty, | |
ea8adc8c | 193 | source_info, |
f9f354fc | 194 | ).internal()); |
dc9dc135 | 195 | let ptr_temp = Place::from(ptr_temp); |
ba9703b0 XL |
196 | let block = unpack!(this.into(ptr_temp, block, ptr)); |
197 | this.into(this.hir.tcx().mk_place_deref(ptr_temp), block, val) | |
cc61c64b | 198 | } else { |
b7449926 XL |
199 | let args: Vec<_> = args |
200 | .into_iter() | |
ba9703b0 | 201 | .map(|arg| unpack!(block = this.as_local_call_operand(block, arg))) |
b7449926 | 202 | .collect(); |
cc61c64b XL |
203 | |
204 | let success = this.cfg.start_new_block(); | |
3b2f2976 | 205 | let cleanup = this.diverge_cleanup(); |
e1599b0c XL |
206 | |
207 | this.record_operands_moved(&args); | |
208 | ||
f035d41b XL |
209 | debug!("into_expr: fn_span={:?}", fn_span); |
210 | ||
b7449926 XL |
211 | this.cfg.terminate( |
212 | block, | |
213 | source_info, | |
214 | TerminatorKind::Call { | |
215 | func: fun, | |
216 | args, | |
217 | cleanup: Some(cleanup), | |
0731742a XL |
218 | // FIXME(varkor): replace this with an uninhabitedness-based check. |
219 | // This requires getting access to the current module to call | |
220 | // `tcx.is_ty_uninhabited_from`, which is currently tricky to do. | |
221 | destination: if expr.ty.is_never() { | |
b7449926 XL |
222 | None |
223 | } else { | |
ba9703b0 | 224 | Some((destination, success)) |
b7449926 | 225 | }, |
0bf4aa26 | 226 | from_hir_call, |
f035d41b | 227 | fn_span |
b7449926 XL |
228 | }, |
229 | ); | |
cc61c64b XL |
230 | success.unit() |
231 | } | |
e9174d1e | 232 | } |
dfeec247 | 233 | ExprKind::Use { source } => this.into(destination, block, source), |
60c5eb7d XL |
234 | ExprKind::Borrow { arg, borrow_kind } => { |
235 | // We don't do this in `as_rvalue` because we use `as_place` | |
236 | // for borrow expressions, so we cannot create an `RValue` that | |
237 | // remains valid across user code. `as_rvalue` is usually called | |
238 | // by this method anyway, so this shouldn't cause too many | |
239 | // unnecessary temporaries. | |
240 | let arg_place = match borrow_kind { | |
241 | BorrowKind::Shared => unpack!(block = this.as_read_only_place(block, arg)), | |
242 | _ => unpack!(block = this.as_place(block, arg)), | |
243 | }; | |
dfeec247 XL |
244 | let borrow = |
245 | Rvalue::Ref(this.hir.tcx().lifetimes.re_erased, borrow_kind, arg_place); | |
60c5eb7d XL |
246 | this.cfg.push_assign(block, source_info, destination, borrow); |
247 | block.unit() | |
248 | } | |
dfeec247 XL |
249 | ExprKind::AddressOf { mutability, arg } => { |
250 | let place = match mutability { | |
251 | hir::Mutability::Not => this.as_read_only_place(block, arg), | |
252 | hir::Mutability::Mut => this.as_place(block, arg), | |
253 | }; | |
254 | let address_of = Rvalue::AddressOf(mutability, unpack!(block = place)); | |
255 | this.cfg.push_assign(block, source_info, destination, address_of); | |
256 | block.unit() | |
257 | } | |
258 | ExprKind::Adt { adt_def, variant_index, substs, user_ty, fields, base } => { | |
60c5eb7d XL |
259 | // See the notes for `ExprKind::Array` in `as_rvalue` and for |
260 | // `ExprKind::Borrow` above. | |
261 | let is_union = adt_def.is_union(); | |
dfeec247 | 262 | let active_field_index = if is_union { Some(fields[0].name.index()) } else { None }; |
60c5eb7d | 263 | |
dfeec247 | 264 | let scope = this.local_scope(); |
60c5eb7d XL |
265 | |
266 | // first process the set of fields that were provided | |
267 | // (evaluating them in order given by user) | |
268 | let fields_map: FxHashMap<_, _> = fields | |
269 | .into_iter() | |
dfeec247 XL |
270 | .map(|f| (f.name, unpack!(block = this.as_operand(block, scope, f.expr)))) |
271 | .collect(); | |
60c5eb7d XL |
272 | |
273 | let field_names = this.hir.all_fields(adt_def, variant_index); | |
274 | ||
ba9703b0 XL |
275 | let fields = if let Some(FruInfo { base, field_types }) = base { |
276 | let base = unpack!(block = this.as_place(block, base)); | |
277 | ||
278 | // MIR does not natively support FRU, so for each | |
279 | // base-supplied field, generate an operand that | |
280 | // reads it from the base. | |
281 | field_names | |
282 | .into_iter() | |
283 | .zip(field_types.into_iter()) | |
284 | .map(|(n, ty)| match fields_map.get(&n) { | |
285 | Some(v) => v.clone(), | |
286 | None => this.consume_by_copy_or_move( | |
287 | this.hir.tcx().mk_place_field(base, n, ty), | |
288 | ), | |
289 | }) | |
290 | .collect() | |
291 | } else { | |
292 | field_names.iter().filter_map(|n| fields_map.get(n).cloned()).collect() | |
293 | }; | |
60c5eb7d XL |
294 | |
295 | let inferred_ty = expr.ty; | |
296 | let user_ty = user_ty.map(|ty| { | |
297 | this.canonical_user_type_annotations.push(CanonicalUserTypeAnnotation { | |
298 | span: source_info.span, | |
299 | user_ty: ty, | |
300 | inferred_ty, | |
301 | }) | |
302 | }); | |
303 | let adt = box AggregateKind::Adt( | |
304 | adt_def, | |
305 | variant_index, | |
306 | substs, | |
307 | user_ty, | |
308 | active_field_index, | |
309 | ); | |
310 | this.cfg.push_assign( | |
311 | block, | |
312 | source_info, | |
313 | destination, | |
dfeec247 | 314 | Rvalue::Aggregate(adt, fields), |
60c5eb7d XL |
315 | ); |
316 | block.unit() | |
317 | } | |
f9f354fc XL |
318 | ExprKind::InlineAsm { template, operands, options, line_spans } => { |
319 | use crate::hair; | |
320 | use rustc_middle::mir; | |
321 | let operands = operands | |
322 | .into_iter() | |
323 | .map(|op| match op { | |
324 | hair::InlineAsmOperand::In { reg, expr } => mir::InlineAsmOperand::In { | |
325 | reg, | |
326 | value: unpack!(block = this.as_local_operand(block, expr)), | |
327 | }, | |
328 | hair::InlineAsmOperand::Out { reg, late, expr } => { | |
329 | mir::InlineAsmOperand::Out { | |
330 | reg, | |
331 | late, | |
332 | place: expr.map(|expr| unpack!(block = this.as_place(block, expr))), | |
333 | } | |
334 | } | |
335 | hair::InlineAsmOperand::InOut { reg, late, expr } => { | |
336 | let place = unpack!(block = this.as_place(block, expr)); | |
337 | mir::InlineAsmOperand::InOut { | |
338 | reg, | |
339 | late, | |
340 | // This works because asm operands must be Copy | |
341 | in_value: Operand::Copy(place), | |
342 | out_place: Some(place), | |
343 | } | |
344 | } | |
345 | hair::InlineAsmOperand::SplitInOut { reg, late, in_expr, out_expr } => { | |
346 | mir::InlineAsmOperand::InOut { | |
347 | reg, | |
348 | late, | |
349 | in_value: unpack!(block = this.as_local_operand(block, in_expr)), | |
350 | out_place: out_expr.map(|out_expr| { | |
351 | unpack!(block = this.as_place(block, out_expr)) | |
352 | }), | |
353 | } | |
354 | } | |
355 | hair::InlineAsmOperand::Const { expr } => mir::InlineAsmOperand::Const { | |
356 | value: unpack!(block = this.as_local_operand(block, expr)), | |
357 | }, | |
358 | hair::InlineAsmOperand::SymFn { expr } => { | |
359 | mir::InlineAsmOperand::SymFn { value: box this.as_constant(expr) } | |
360 | } | |
f035d41b XL |
361 | hair::InlineAsmOperand::SymStatic { def_id } => { |
362 | mir::InlineAsmOperand::SymStatic { def_id } | |
f9f354fc XL |
363 | } |
364 | }) | |
365 | .collect(); | |
366 | ||
367 | let destination = this.cfg.start_new_block(); | |
368 | ||
369 | this.cfg.terminate( | |
370 | block, | |
371 | source_info, | |
372 | TerminatorKind::InlineAsm { | |
373 | template, | |
374 | operands, | |
375 | options, | |
376 | line_spans, | |
377 | destination: if options.contains(InlineAsmOptions::NORETURN) { | |
378 | None | |
379 | } else { | |
380 | Some(destination) | |
381 | }, | |
382 | }, | |
383 | ); | |
384 | destination.unit() | |
385 | } | |
60c5eb7d | 386 | |
a7813a04 | 387 | // These cases don't actually need a destination |
b7449926 XL |
388 | ExprKind::Assign { .. } |
389 | | ExprKind::AssignOp { .. } | |
390 | | ExprKind::Continue { .. } | |
391 | | ExprKind::Break { .. } | |
ba9703b0 | 392 | | ExprKind::LlvmInlineAsm { .. } |
b7449926 | 393 | | ExprKind::Return { .. } => { |
a1dfa0c6 | 394 | unpack!(block = this.stmt_expr(block, expr, None)); |
ba9703b0 | 395 | this.cfg.push_assign_unit(block, source_info, destination, this.hir.tcx()); |
ff7c6d11 | 396 | block.unit() |
a7813a04 XL |
397 | } |
398 | ||
8faf50e0 | 399 | // Avoid creating a temporary |
dfeec247 XL |
400 | ExprKind::VarRef { .. } |
401 | | ExprKind::SelfRef | |
402 | | ExprKind::PlaceTypeAscription { .. } | |
403 | | ExprKind::ValueTypeAscription { .. } => { | |
8faf50e0 XL |
404 | debug_assert!(Category::of(&expr.kind) == Some(Category::Place)); |
405 | ||
406 | let place = unpack!(block = this.as_place(block, expr)); | |
407 | let rvalue = Rvalue::Use(this.consume_by_copy_or_move(place)); | |
dfeec247 | 408 | this.cfg.push_assign(block, source_info, destination, rvalue); |
8faf50e0 XL |
409 | block.unit() |
410 | } | |
b7449926 | 411 | ExprKind::Index { .. } | ExprKind::Deref { .. } | ExprKind::Field { .. } => { |
8faf50e0 XL |
412 | debug_assert!(Category::of(&expr.kind) == Some(Category::Place)); |
413 | ||
414 | // Create a "fake" temporary variable so that we check that the | |
415 | // value is Sized. Usually, this is caught in type checking, but | |
416 | // in the case of box expr there is no such check. | |
e1599b0c | 417 | if !destination.projection.is_empty() { |
f9f354fc | 418 | this.local_decls.push(LocalDecl::new(expr.ty, expr.span)); |
8faf50e0 XL |
419 | } |
420 | ||
421 | debug_assert!(Category::of(&expr.kind) == Some(Category::Place)); | |
422 | ||
423 | let place = unpack!(block = this.as_place(block, expr)); | |
424 | let rvalue = Rvalue::Use(this.consume_by_copy_or_move(place)); | |
dfeec247 | 425 | this.cfg.push_assign(block, source_info, destination, rvalue); |
8faf50e0 XL |
426 | block.unit() |
427 | } | |
428 | ||
74b04a01 XL |
429 | ExprKind::Yield { value } => { |
430 | let scope = this.local_scope(); | |
431 | let value = unpack!(block = this.as_operand(block, scope, value)); | |
432 | let resume = this.cfg.start_new_block(); | |
433 | let cleanup = this.generator_drop_cleanup(); | |
434 | this.cfg.terminate( | |
435 | block, | |
436 | source_info, | |
ba9703b0 | 437 | TerminatorKind::Yield { value, resume, resume_arg: destination, drop: cleanup }, |
74b04a01 XL |
438 | ); |
439 | resume.unit() | |
440 | } | |
441 | ||
e9174d1e | 442 | // these are the cases that are more naturally handled by some other mode |
b7449926 XL |
443 | ExprKind::Unary { .. } |
444 | | ExprKind::Binary { .. } | |
445 | | ExprKind::Box { .. } | |
446 | | ExprKind::Cast { .. } | |
48663c56 | 447 | | ExprKind::Pointer { .. } |
b7449926 | 448 | | ExprKind::Repeat { .. } |
b7449926 XL |
449 | | ExprKind::Array { .. } |
450 | | ExprKind::Tuple { .. } | |
b7449926 XL |
451 | | ExprKind::Closure { .. } |
452 | | ExprKind::Literal { .. } | |
f9f354fc | 453 | | ExprKind::ThreadLocalRef(_) |
74b04a01 | 454 | | ExprKind::StaticRef { .. } => { |
e9174d1e | 455 | debug_assert!(match Category::of(&expr.kind).unwrap() { |
0bf4aa26 | 456 | // should be handled above |
e9174d1e | 457 | Category::Rvalue(RvalueFunc::Into) => false, |
0bf4aa26 XL |
458 | |
459 | // must be handled above or else we get an | |
460 | // infinite loop in the builder; see | |
0731742a | 461 | // e.g., `ExprKind::VarRef` above |
0bf4aa26 XL |
462 | Category::Place => false, |
463 | ||
e9174d1e SL |
464 | _ => true, |
465 | }); | |
466 | ||
8bb4bdeb | 467 | let rvalue = unpack!(block = this.as_local_rvalue(block, expr)); |
0731742a | 468 | this.cfg.push_assign(block, source_info, destination, rvalue); |
e9174d1e SL |
469 | block.unit() |
470 | } | |
0bf4aa26 XL |
471 | }; |
472 | ||
473 | if !expr_is_block_or_scope { | |
474 | let popped = this.block_context.pop(); | |
475 | assert!(popped.is_some()); | |
e9174d1e | 476 | } |
0bf4aa26 XL |
477 | |
478 | block_and | |
e9174d1e | 479 | } |
e9174d1e | 480 | } |