]> git.lizzy.rs Git - rust.git/blob - src/librustc/middle/trans/tvec.rs
586b29c19c90cf295a73db0b2dd57e83895b2d30
[rust.git] / src / librustc / middle / trans / tvec.rs
1 // Copyright 2012 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11
12 use back::abi;
13 use lib;
14 use lib::llvm::{llvm, ValueRef, TypeRef};
15 use middle::trans::base;
16 use middle::trans::base::*;
17 use middle::trans::build::*;
18 use middle::trans::callee;
19 use middle::trans::common::*;
20 use middle::trans::datum::*;
21 use middle::trans::expr::{Dest, Ignore, SaveIn};
22 use middle::trans::expr;
23 use middle::trans::glue;
24 use middle::trans::machine::{llsize_of, nonzero_llsize_of};
25 use middle::trans::type_of;
26 use middle::ty;
27 use util::common::indenter;
28 use util::ppaux::ty_to_str;
29
30 use core::uint;
31 use core::vec;
32 use syntax::ast;
33 use syntax::codemap;
34
35 // Boxed vector types are in some sense currently a "shorthand" for a box
36 // containing an unboxed vector. This expands a boxed vector type into such an
37 // expanded type. It doesn't respect mutability, but that doesn't matter at
38 // this point.
39 pub fn expand_boxed_vec_ty(tcx: ty::ctxt, t: ty::t) -> ty::t {
40     let unit_ty = ty::sequence_element_type(tcx, t);
41     let unboxed_vec_ty = ty::mk_mut_unboxed_vec(tcx, unit_ty);
42     match ty::get(t).sty {
43       ty::ty_estr(ty::vstore_uniq) | ty::ty_evec(_, ty::vstore_uniq) => {
44         ty::mk_imm_uniq(tcx, unboxed_vec_ty)
45       }
46       ty::ty_estr(ty::vstore_box) | ty::ty_evec(_, ty::vstore_box) => {
47         ty::mk_imm_box(tcx, unboxed_vec_ty)
48       }
49       _ => tcx.sess.bug(~"non boxed-vec type \
50                           in tvec::expand_boxed_vec_ty")
51     }
52 }
53
54 pub fn get_fill(bcx: block, vptr: ValueRef) -> ValueRef {
55     let _icx = bcx.insn_ctxt("tvec::get_fill");
56     Load(bcx, GEPi(bcx, vptr, [0u, abi::vec_elt_fill]))
57 }
58 pub fn set_fill(bcx: block, vptr: ValueRef, fill: ValueRef) {
59     Store(bcx, fill, GEPi(bcx, vptr, [0u, abi::vec_elt_fill]));
60 }
61 pub fn get_alloc(bcx: block, vptr: ValueRef) -> ValueRef {
62     Load(bcx, GEPi(bcx, vptr, [0u, abi::vec_elt_alloc]))
63 }
64
65 pub fn get_bodyptr(bcx: block, vptr: ValueRef) -> ValueRef {
66     base::non_gc_box_cast(bcx, GEPi(bcx, vptr, [0u, abi::box_field_body]))
67 }
68
69 pub fn get_dataptr(bcx: block, vptr: ValueRef) -> ValueRef {
70     let _icx = bcx.insn_ctxt("tvec::get_dataptr");
71     GEPi(bcx, vptr, [0u, abi::vec_elt_elems, 0u])
72 }
73
74 pub fn pointer_add(bcx: block, ptr: ValueRef, bytes: ValueRef) -> ValueRef {
75     let _icx = bcx.insn_ctxt("tvec::pointer_add");
76     let old_ty = val_ty(ptr);
77     let bptr = PointerCast(bcx, ptr, T_ptr(T_i8()));
78     return PointerCast(bcx, InBoundsGEP(bcx, bptr, ~[bytes]), old_ty);
79 }
80
81 pub fn alloc_raw(bcx: block, unit_ty: ty::t,
82                  fill: ValueRef, alloc: ValueRef, heap: heap) -> Result {
83     let _icx = bcx.insn_ctxt("tvec::alloc_uniq");
84     let ccx = bcx.ccx();
85
86     let vecbodyty = ty::mk_mut_unboxed_vec(bcx.tcx(), unit_ty);
87     let vecsize = Add(bcx, alloc, llsize_of(ccx, ccx.opaque_vec_type));
88
89     let base::MallocResult {bcx, box: bx, body} =
90         base::malloc_general_dyn(bcx, vecbodyty, heap, vecsize);
91     Store(bcx, fill, GEPi(bcx, body, [0u, abi::vec_elt_fill]));
92     Store(bcx, alloc, GEPi(bcx, body, [0u, abi::vec_elt_alloc]));
93     base::maybe_set_managed_unique_rc(bcx, bx, heap);
94     return rslt(bcx, bx);
95 }
96
97 pub fn alloc_uniq_raw(bcx: block, unit_ty: ty::t,
98                       fill: ValueRef, alloc: ValueRef) -> Result {
99     alloc_raw(bcx, unit_ty, fill, alloc, base::heap_for_unique(bcx, unit_ty))
100 }
101
102 pub fn alloc_vec(bcx: block,
103                  unit_ty: ty::t,
104                  elts: uint,
105                  heap: heap)
106               -> Result {
107     let _icx = bcx.insn_ctxt("tvec::alloc_uniq");
108     let ccx = bcx.ccx();
109     let llunitty = type_of::type_of(ccx, unit_ty);
110     let unit_sz = nonzero_llsize_of(ccx, llunitty);
111
112     let fill = Mul(bcx, C_uint(ccx, elts), unit_sz);
113     let alloc = if elts < 4u { Mul(bcx, C_int(ccx, 4), unit_sz) }
114                 else { fill };
115     let Result {bcx: bcx, val: vptr} =
116         alloc_raw(bcx, unit_ty, fill, alloc, heap);
117     return rslt(bcx, vptr);
118 }
119
120 pub fn duplicate_uniq(bcx: block, vptr: ValueRef, vec_ty: ty::t) -> Result {
121     let _icx = bcx.insn_ctxt("tvec::duplicate_uniq");
122
123     let fill = get_fill(bcx, get_bodyptr(bcx, vptr));
124     let unit_ty = ty::sequence_element_type(bcx.tcx(), vec_ty);
125     let Result {bcx, val: newptr} = alloc_uniq_raw(bcx, unit_ty, fill, fill);
126
127     let data_ptr = get_dataptr(bcx, get_bodyptr(bcx, vptr));
128     let new_data_ptr = get_dataptr(bcx, get_bodyptr(bcx, newptr));
129     base::call_memcpy(bcx, new_data_ptr, data_ptr, fill);
130
131     let bcx = if ty::type_needs_drop(bcx.tcx(), unit_ty) {
132         iter_vec_raw(bcx, new_data_ptr, vec_ty, fill, glue::take_ty)
133     } else { bcx };
134     return rslt(bcx, newptr);
135 }
136
137 pub fn make_drop_glue_unboxed(bcx: block, vptr: ValueRef, vec_ty: ty::t) ->
138    block {
139     let _icx = bcx.insn_ctxt("tvec::make_drop_glue_unboxed");
140     let tcx = bcx.tcx(), unit_ty = ty::sequence_element_type(tcx, vec_ty);
141     if ty::type_needs_drop(tcx, unit_ty) {
142         iter_vec_unboxed(bcx, vptr, vec_ty, glue::drop_ty)
143     } else { bcx }
144 }
145
146 pub struct VecTypes {
147     vec_ty: ty::t,
148     unit_ty: ty::t,
149     llunit_ty: TypeRef,
150     llunit_size: ValueRef
151 }
152
153 pub impl VecTypes {
154     fn to_str(&self, ccx: @CrateContext) -> ~str {
155         fmt!("VecTypes {vec_ty=%s, unit_ty=%s, llunit_ty=%s, llunit_size=%s}",
156              ty_to_str(ccx.tcx, self.vec_ty),
157              ty_to_str(ccx.tcx, self.unit_ty),
158              ty_str(ccx.tn, self.llunit_ty),
159              val_str(ccx.tn, self.llunit_size))
160     }
161 }
162
163 pub fn trans_fixed_vstore(bcx: block,
164                           vstore_expr: @ast::expr,
165                           content_expr: @ast::expr,
166                           dest: expr::Dest)
167                        -> block {
168     //!
169     //
170     // [...] allocates a fixed-size array and moves it around "by value".
171     // In this case, it means that the caller has already given us a location
172     // to store the array of the suitable size, so all we have to do is
173     // generate the content.
174
175     debug!("trans_fixed_vstore(vstore_expr=%s, dest=%?)",
176            bcx.expr_to_str(vstore_expr), dest.to_str(bcx.ccx()));
177     let _indenter = indenter();
178
179     let vt = vec_types_from_expr(bcx, vstore_expr);
180
181     return match dest {
182         Ignore => write_content(bcx, &vt, vstore_expr, content_expr, dest),
183         SaveIn(lldest) => {
184             // lldest will have type *[T x N], but we want the type *T,
185             // so use GEP to convert:
186             let lldest = GEPi(bcx, lldest, [0, 0]);
187             write_content(bcx, &vt, vstore_expr, content_expr, SaveIn(lldest))
188         }
189     };
190 }
191
192 pub fn trans_slice_vstore(bcx: block,
193                           vstore_expr: @ast::expr,
194                           content_expr: @ast::expr,
195                           dest: expr::Dest)
196                        -> block {
197     //!
198     //
199     // &[...] allocates memory on the stack and writes the values into it,
200     // returning a slice (pair of ptr, len).  &"..." is similar except that
201     // the memory can be statically allocated.
202
203     let ccx = bcx.ccx();
204
205     debug!("trans_slice_vstore(vstore_expr=%s, dest=%s)",
206            bcx.expr_to_str(vstore_expr), dest.to_str(ccx));
207     let _indenter = indenter();
208
209     // Handle the &"..." case:
210     match content_expr.node {
211         ast::expr_lit(@codemap::spanned {node: ast::lit_str(s), span: _}) => {
212             return trans_lit_str(bcx, content_expr, s, dest);
213         }
214         _ => {}
215     }
216
217     // Handle the &[...] case:
218     let vt = vec_types_from_expr(bcx, vstore_expr);
219     let count = elements_required(bcx, content_expr);
220     debug!("vt=%s, count=%?", vt.to_str(ccx), count);
221
222     // Make a fixed-length backing array and allocate it on the stack.
223     let llcount = C_uint(ccx, count);
224     let llfixed = base::arrayalloca(bcx, vt.llunit_ty, llcount);
225
226     // Arrange for the backing array to be cleaned up.
227     let fixed_ty = ty::mk_evec(bcx.tcx(),
228                                ty::mt {ty: vt.unit_ty, mutbl: ast::m_mutbl},
229                                ty::vstore_fixed(count));
230     let llfixed_ty = T_ptr(type_of::type_of(bcx.ccx(), fixed_ty));
231     let llfixed_casted = BitCast(bcx, llfixed, llfixed_ty);
232     add_clean(bcx, llfixed_casted, fixed_ty);
233
234     // Generate the content into the backing array.
235     let bcx = write_content(bcx, &vt, vstore_expr,
236                             content_expr, SaveIn(llfixed));
237
238     // Finally, create the slice pair itself.
239     match dest {
240         Ignore => {}
241         SaveIn(lldest) => {
242             Store(bcx, llfixed, GEPi(bcx, lldest, [0u, abi::slice_elt_base]));
243             let lllen = Mul(bcx, llcount, vt.llunit_size);
244             Store(bcx, lllen, GEPi(bcx, lldest, [0u, abi::slice_elt_len]));
245         }
246     }
247
248     return bcx;
249 }
250
251 pub fn trans_lit_str(bcx: block,
252                      lit_expr: @ast::expr,
253                      str_lit: @~str,
254                      dest: Dest)
255                   -> block {
256     //!
257     //
258     // Literal strings translate to slices into static memory.  This is
259     // different from trans_slice_vstore() above because it does need to copy
260     // the content anywhere.
261
262     debug!("trans_lit_str(lit_expr=%s, dest=%s)",
263            bcx.expr_to_str(lit_expr),
264            dest.to_str(bcx.ccx()));
265     let _indenter = indenter();
266
267     match dest {
268         Ignore => bcx,
269         SaveIn(lldest) => {
270             unsafe {
271                 let bytes = str_lit.len() + 1; // count null-terminator too
272                 let llbytes = C_uint(bcx.ccx(), bytes);
273                 let llcstr = C_cstr(bcx.ccx(), str_lit);
274                 let llcstr = llvm::LLVMConstPointerCast(llcstr,
275                                                         T_ptr(T_i8()));
276                 Store(bcx,
277                       llcstr,
278                       GEPi(bcx, lldest, [0u, abi::slice_elt_base]));
279                 Store(bcx,
280                       llbytes,
281                       GEPi(bcx, lldest, [0u, abi::slice_elt_len]));
282                 bcx
283             }
284         }
285     }
286 }
287
288
289 pub fn trans_uniq_or_managed_vstore(bcx: block,
290                                     heap: heap,
291                                     vstore_expr: @ast::expr,
292                                     content_expr: @ast::expr) -> DatumBlock {
293     //!
294     //
295     // @[...] or ~[...] (also @"..." or ~"...") allocate boxes in the
296     // appropriate heap and write the array elements into them.
297
298     debug!("trans_uniq_or_managed_vstore(vstore_expr=%s, heap=%?)",
299            bcx.expr_to_str(vstore_expr), heap);
300     let _indenter = indenter();
301
302     // Handle ~"".
303     match heap {
304         heap_exchange => {
305             match content_expr.node {
306                 ast::expr_lit(@codemap::spanned {
307                     node: ast::lit_str(s), _
308                 }) => {
309                     let llptrval = C_cstr(bcx.ccx(), s);
310                     let llptrval = PointerCast(bcx, llptrval, T_ptr(T_i8()));
311                     let llsizeval = C_uint(bcx.ccx(), s.len());
312                     let typ = ty::mk_estr(bcx.tcx(), ty::vstore_uniq);
313                     let lldestval = scratch_datum(bcx, typ, false);
314                     let bcx = callee::trans_lang_call(
315                         bcx,
316                         bcx.tcx().lang_items.strdup_uniq_fn(),
317                         ~[ llptrval, llsizeval ],
318                         expr::SaveIn(lldestval.to_ref_llval(bcx)));
319                     return DatumBlock {
320                         bcx: bcx,
321                         datum: lldestval
322                     };
323                 }
324                 _ => {}
325             }
326         }
327         heap_managed | heap_managed_unique => {}
328     }
329
330     let vt = vec_types_from_expr(bcx, vstore_expr);
331     let count = elements_required(bcx, content_expr);
332
333     let Result {bcx, val} = alloc_vec(bcx, vt.unit_ty, count, heap);
334
335     add_clean_free(bcx, val, heap);
336     let dataptr = get_dataptr(bcx, get_bodyptr(bcx, val));
337
338     debug!("alloc_vec() returned val=%s, dataptr=%s",
339            bcx.val_str(val), bcx.val_str(dataptr));
340
341     let bcx = write_content(bcx, &vt, vstore_expr,
342                             content_expr, SaveIn(dataptr));
343
344     revoke_clean(bcx, val);
345
346     return immediate_rvalue_bcx(bcx, val, vt.vec_ty);
347 }
348
349 pub fn write_content(bcx: block,
350                      vt: &VecTypes,
351                      vstore_expr: @ast::expr,
352                      content_expr: @ast::expr,
353                      dest: Dest)
354                   -> block {
355     let _icx = bcx.insn_ctxt("tvec::write_content");
356     let mut bcx = bcx;
357
358     debug!("write_content(vt=%s, dest=%s, vstore_expr=%?)",
359            vt.to_str(bcx.ccx()),
360            dest.to_str(bcx.ccx()),
361            bcx.expr_to_str(vstore_expr));
362     let _indenter = indenter();
363
364     match content_expr.node {
365         ast::expr_lit(@codemap::spanned { node: ast::lit_str(s), _ }) => {
366             match dest {
367                 Ignore => {
368                     return bcx;
369                 }
370                 SaveIn(lldest) => {
371                     let bytes = s.len() + 1; // copy null-terminator too
372                     let llbytes = C_uint(bcx.ccx(), bytes);
373                     let llcstr = C_cstr(bcx.ccx(), s);
374                     base::call_memcpy(bcx, lldest, llcstr, llbytes);
375                     return bcx;
376                 }
377             }
378         }
379         ast::expr_vec(ref elements, _) => {
380             match dest {
381                 Ignore => {
382                     for elements.each |element| {
383                         bcx = expr::trans_into(bcx, *element, Ignore);
384                     }
385                 }
386
387                 SaveIn(lldest) => {
388                     let mut temp_cleanups = ~[];
389                     for elements.eachi |i, element| {
390                         let lleltptr = GEPi(bcx, lldest, [i]);
391                         debug!("writing index %? with lleltptr=%?",
392                                i, bcx.val_str(lleltptr));
393                         bcx = expr::trans_into(bcx, *element,
394                                                SaveIn(lleltptr));
395                         add_clean_temp_mem(bcx, lleltptr, vt.unit_ty);
396                         temp_cleanups.push(lleltptr);
397                     }
398                     for vec::each(temp_cleanups) |cleanup| {
399                         revoke_clean(bcx, *cleanup);
400                     }
401                 }
402             }
403             return bcx;
404         }
405         ast::expr_repeat(element, count_expr, _) => {
406             match dest {
407                 Ignore => {
408                     return expr::trans_into(bcx, element, Ignore);
409                 }
410                 SaveIn(lldest) => {
411                     let count = ty::eval_repeat_count(bcx.tcx(), count_expr);
412                     if count == 0 {
413                         return bcx;
414                     }
415
416                     let tmpdatum = unpack_datum!(bcx, {
417                         expr::trans_to_datum(bcx, element)
418                     });
419
420                     let mut temp_cleanups = ~[];
421
422                     for uint::range(0, count) |i| {
423                         let lleltptr = GEPi(bcx, lldest, [i]);
424                         if i < count - 1 {
425                             // Copy all but the last one in.
426                             bcx = tmpdatum.copy_to(bcx, INIT, lleltptr);
427                         } else {
428                             // Move the last one in.
429                             bcx = tmpdatum.move_to(bcx, INIT, lleltptr);
430                         }
431                         add_clean_temp_mem(bcx, lleltptr, vt.unit_ty);
432                         temp_cleanups.push(lleltptr);
433                     }
434
435                     for vec::each(temp_cleanups) |cleanup| {
436                         revoke_clean(bcx, *cleanup);
437                     }
438
439                     return bcx;
440                 }
441             }
442         }
443         _ => {
444             bcx.tcx().sess.span_bug(content_expr.span,
445                                     ~"Unexpected evec content");
446         }
447     }
448 }
449
450 pub fn vec_types_from_expr(bcx: block, vec_expr: @ast::expr) -> VecTypes {
451     let vec_ty = node_id_type(bcx, vec_expr.id);
452     vec_types(bcx, vec_ty)
453 }
454
455 pub fn vec_types(bcx: block, vec_ty: ty::t) -> VecTypes {
456     let ccx = bcx.ccx();
457     let unit_ty = ty::sequence_element_type(bcx.tcx(), vec_ty);
458     let llunit_ty = type_of::type_of(ccx, unit_ty);
459     let llunit_size = nonzero_llsize_of(ccx, llunit_ty);
460
461     VecTypes {vec_ty: vec_ty,
462               unit_ty: unit_ty,
463               llunit_ty: llunit_ty,
464               llunit_size: llunit_size}
465 }
466
467 pub fn elements_required(bcx: block, content_expr: @ast::expr) -> uint {
468     //! Figure out the number of elements we need to store this content
469
470     match content_expr.node {
471         ast::expr_lit(@codemap::spanned { node: ast::lit_str(s), _ }) => {
472             s.len() + 1
473         },
474         ast::expr_vec(ref es, _) => es.len(),
475         ast::expr_repeat(_, count_expr, _) => {
476             ty::eval_repeat_count(bcx.tcx(), count_expr)
477         }
478         _ => bcx.tcx().sess.span_bug(content_expr.span,
479                                      ~"Unexpected evec content")
480     }
481 }
482
483 pub fn get_base_and_len(bcx: block,
484                         llval: ValueRef,
485                         vec_ty: ty::t) -> (ValueRef, ValueRef) {
486     //!
487     //
488     // Converts a vector into the slice pair.  The vector should be stored in
489     // `llval` which should be either immediate or by-ref as appropriate for
490     // the vector type.  If you have a datum, you would probably prefer to
491     // call `Datum::get_base_and_len()` which will handle any conversions for
492     // you.
493
494     let ccx = bcx.ccx();
495     let vt = vec_types(bcx, vec_ty);
496
497     let vstore = match ty::get(vt.vec_ty).sty {
498       ty::ty_estr(vst) | ty::ty_evec(_, vst) => vst,
499       _ => ty::vstore_uniq
500     };
501
502     match vstore {
503         ty::vstore_fixed(n) => {
504             let base = GEPi(bcx, llval, [0u, 0u]);
505             let n = if ty::type_is_str(vec_ty) { n + 1u } else { n };
506             let len = Mul(bcx, C_uint(ccx, n), vt.llunit_size);
507             (base, len)
508         }
509         ty::vstore_slice(_) => {
510             let base = Load(bcx, GEPi(bcx, llval, [0u, abi::slice_elt_base]));
511             let len = Load(bcx, GEPi(bcx, llval, [0u, abi::slice_elt_len]));
512             (base, len)
513         }
514         ty::vstore_uniq | ty::vstore_box => {
515             let body = get_bodyptr(bcx, llval);
516             (get_dataptr(bcx, body), get_fill(bcx, body))
517         }
518     }
519 }
520
521 pub type val_and_ty_fn = @fn(block, ValueRef, ty::t) -> Result;
522
523 pub type iter_vec_block = &'self fn(block, ValueRef, ty::t) -> block;
524
525 pub fn iter_vec_raw(bcx: block, data_ptr: ValueRef, vec_ty: ty::t,
526                     fill: ValueRef, f: iter_vec_block) -> block {
527     let _icx = bcx.insn_ctxt("tvec::iter_vec_raw");
528
529     let unit_ty = ty::sequence_element_type(bcx.tcx(), vec_ty);
530
531     // Calculate the last pointer address we want to handle.
532     // FIXME (#3729): Optimize this when the size of the unit type is
533     // statically known to not use pointer casts, which tend to confuse
534     // LLVM.
535     let data_end_ptr = pointer_add(bcx, data_ptr, fill);
536
537     // Now perform the iteration.
538     let header_bcx = base::sub_block(bcx, ~"iter_vec_loop_header");
539     Br(bcx, header_bcx.llbb);
540     let data_ptr =
541         Phi(header_bcx, val_ty(data_ptr), ~[data_ptr], ~[bcx.llbb]);
542     let not_yet_at_end =
543         ICmp(header_bcx, lib::llvm::IntULT, data_ptr, data_end_ptr);
544     let body_bcx = base::sub_block(header_bcx, ~"iter_vec_loop_body");
545     let next_bcx = base::sub_block(header_bcx, ~"iter_vec_next");
546     CondBr(header_bcx, not_yet_at_end, body_bcx.llbb, next_bcx.llbb);
547     let body_bcx = f(body_bcx, data_ptr, unit_ty);
548     AddIncomingToPhi(data_ptr, InBoundsGEP(body_bcx, data_ptr,
549                                            ~[C_int(bcx.ccx(), 1)]),
550                      body_bcx.llbb);
551     Br(body_bcx, header_bcx.llbb);
552     return next_bcx;
553
554 }
555
556 pub fn iter_vec_uniq(bcx: block, vptr: ValueRef, vec_ty: ty::t,
557                      fill: ValueRef, f: iter_vec_block) -> block {
558     let _icx = bcx.insn_ctxt("tvec::iter_vec_uniq");
559     let data_ptr = get_dataptr(bcx, get_bodyptr(bcx, vptr));
560     iter_vec_raw(bcx, data_ptr, vec_ty, fill, f)
561 }
562
563 pub fn iter_vec_unboxed(bcx: block, body_ptr: ValueRef, vec_ty: ty::t,
564                         f: iter_vec_block) -> block {
565     let _icx = bcx.insn_ctxt("tvec::iter_vec_unboxed");
566     let fill = get_fill(bcx, body_ptr);
567     let dataptr = get_dataptr(bcx, body_ptr);
568     return iter_vec_raw(bcx, dataptr, vec_ty, fill, f);
569 }
570
571 //
572 // Local Variables:
573 // mode: rust
574 // fill-column: 78;
575 // indent-tabs-mode: nil
576 // c-basic-offset: 4
577 // buffer-file-coding-system: utf-8-unix
578 // End:
579 //