aboutsummaryrefslogtreecommitdiffstats
path: root/cparser/Unblock.ml
blob: 4b1f22629439003e4298774d427296e0b19879f8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
(* *********************************************************************)
(*                                                                     *)
(*              The Compcert verified compiler                         *)
(*                                                                     *)
(*          Xavier Leroy, INRIA Paris-Rocquencourt                     *)
(*                                                                     *)
(*  Copyright Institut National de Recherche en Informatique et en     *)
(*  Automatique.  All rights reserved.  This file is distributed       *)
(*  under the terms of the GNU Lesser General Public License as        *)
(*  published by the Free Software Foundation, either version 2.1 of   *)
(*  the License, or  (at your option) any later version.               *)
(*  This file is also distributed under the terms of the               *)
(*  INRIA Non-Commercial License Agreement.                            *)
(*                                                                     *)
(* *********************************************************************)

(* Simplification of blocks and initializers within functions *)

(* Assumes: nothing
   Produces: unblocked code *)

open C
open Cutil

(* Convert an initializer to a list of assignment expressions. *)

let rec local_initializer env path init k =
  match init with
  | Init_single e ->
      { edesc = EBinop(Oassign, path, e, path.etyp); etyp = path.etyp } :: k
  | Init_array il ->
      let (ty_elt, sz) =
        match unroll env path.etyp with
        | TArray(ty_elt, Some sz, _) -> (ty_elt, sz)
        (* We accept empty array initializer for flexible array members, which
           has size zero *)
        | TArray(ty_elt, None, _) when il = [] -> (ty_elt, 0L)
        | _ -> Diagnostics.fatal_error Diagnostics.no_loc "wrong type for array initializer" in
      let rec array_init pos il =
        if pos >= sz then k else begin
          let (i1, il') =
            match il with
            | [] -> (default_init env ty_elt, [])
            | i1 :: il' -> (i1, il') in
          local_initializer env
            { edesc = EBinop(Oindex, path, intconst pos IInt, TPtr(ty_elt, []));
              etyp = ty_elt }
            i1
            (array_init (Int64.succ pos) il')
        end in
      array_init 0L il
  | Init_struct(id, fil) ->
      let field_init (fld, i) k =
        local_initializer env
          { edesc = EUnop(Odot fld.fld_name, path); etyp = fld.fld_typ }
          i k in
      List.fold_right field_init fil k
  | Init_union(id, fld, i) ->
      local_initializer env
        { edesc = EUnop(Odot fld.fld_name, path); etyp = fld.fld_typ }
        i k

(* Prepend assignments to the given statement. *)

let add_inits_stmt loc inits s =
  List.fold_right
    (fun e s -> sseq loc {sdesc = Sdo e; sloc = loc} s)
    inits s

(* Record new variables to be locally or globally defined *)

let local_variables = ref ([]: decl list)
let global_variables = ref ([]: decl list)

(* Note: "const int x = y - 1;" is legal, but we turn it into
   "const int x; x = y - 1;", which is not.  Therefore, remove
   top-level 'const' attribute.  Also remove it on element type of
   array type. *)

let remove_const env ty = remove_attributes_type env [AConst] ty

(* Process a compound literal "(ty) { init }".
   At top-level, within an initializer for a global variable,
   it gives rise to a static global definition of a fresh variable,
   initialized with "init".  The compound variable is replaced
   by the fresh variable.
   Within a function, it gives rise to a local variable
   and an explicit initialization at the nearest sequence point. *)

let process_compound_literal islocal env ty init =
  let id = Env.fresh_ident "__compound" in
  if islocal then begin
    let ty' = remove_const env ty in
    let e = {edesc = EVar id; etyp = ty'} in
    local_variables :=
      (Storage_default, id, ty', None) :: !local_variables;
    (local_initializer env e init [], e)
  end else begin
    global_variables :=
      (Storage_static, id, ty, Some init) :: !global_variables;
    ([], {edesc = EVar id; etyp = ty})
  end

(* Elimination of compound literals within an expression.
   Compound literals are turned into fresh variables, recorded in
   [local_variables] or [global_variables] depending on [islocal].
   For local variables, initializing assignments are added before
   the expression and after sequence points in the expression.
   Use only if [e] is a r-value. *)

let rec expand_expr islocal env e =
  let inits = ref [] in   (* accumulator for initializing assignments *)
  let rec expand e =
    match e.edesc with
    | EConst _ | ESizeof _ | EAlignof _ | EVar _ -> e
    | EUnop(op, e1) ->
        {edesc = EUnop(op, expand e1); etyp = e.etyp}
    | EBinop(op, e1, e2, ty) ->
        let e1' = expand e1 in
        let e2' =
          match op with
          | Ocomma | Ologand | Ologor -> expand_expr islocal env e2
              (* Make sure the initializers of [e2] are performed in
                 sequential order, i.e. just before [e2] but after [e1]. *)
          | _ -> expand e2 in
        {edesc = EBinop(op, e1', e2', ty); etyp = e.etyp}
    | EConditional(e1, e2, e3) ->
        (* Same remark as above: initializers of [e2] and [e3] must
           be performed after the conditional is resolved. *)
        {edesc = EConditional(expand e1,
                              expand_expr islocal env e2,
                              expand_expr islocal env e3);
         etyp = e.etyp}
    | ECast(ty, e1) ->
        {edesc = ECast(ty, expand e1); etyp = e.etyp}
    | ECompound(ty, ie) ->
        let ie' = expand_init islocal env ie in
        let (l, e') = process_compound_literal islocal env ty ie' in
        inits := l @ !inits;
        e'
    | ECall(e1, el) ->
        {edesc = ECall(expand e1, List.map expand el); etyp = e.etyp}
  in
    let e' = expand e in ecommalist !inits e'

(* Elimination of compound literals within an initializer. *)

and expand_init islocal env i =
  let rec expand i =
    match i with
    (* The following "flattening" is not C99.  GCC documents it; whether
       it implements it is unclear.  Clang implements it.  At any rate,
       it makes it possible to use compound literals in static initializers,
       something that is not possible in C99 because compound literals
       are not constant expressions.
       Note that flattening is done for structs and unions but not for
       arrays, because a compound literal of array type in r-value position
       decays to a pointer to its first element. *)
    | Init_single {edesc = ECompound(_, ((Init_struct _ | Init_union _) as i))} ->
        expand i
    | Init_single e ->
        Init_single (expand_expr islocal env e)
    | Init_array il ->
        Init_array (List.rev (List.rev_map expand il))
    | Init_struct(id, flds) ->
        Init_struct(id, List.map (fun (f, i) -> (f, expand i)) flds)
    | Init_union(id, fld, i) ->
        Init_union(id, fld, expand i)
  in
    expand i

(* Insertion of debug annotation, for -g mode *)

let debug_id = Env.fresh_ident "__builtin_debug"
let debug_ty =
  TFun(TVoid [], Some [Env.fresh_ident "kind", TInt(IInt, [])], true, [])

let debug_annot kind args =
  { sloc = no_loc;
    sdesc = Sdo {
      etyp = TVoid [];
      edesc = ECall({edesc = EVar debug_id; etyp = debug_ty},
                    intconst kind IInt :: args)
    }
  }

let string_const str =
  let c = CStr str in { edesc = EConst c; etyp = type_of_constant c }

let integer_const n =
  intconst (Int64.of_int n) IInt

(* Line number annotation:
      __builtin_debug(1, "#line:filename:lineno") *)
(* TODO: consider
      __builtin_debug(1, "filename", lineno)
   instead. *)

let debug_lineno (filename, lineno) =
  debug_annot 1L
    [string_const (Printf.sprintf "#line:%s:%d" filename lineno)]

(* Scope annotation:
      __builtin_debug(6, "", scope1, scope2, ..., scopeN)
*)

let empty_string = string_const ""

let curr_fun_id = ref 0

let debug_var_decl ctx id =
  Debug.add_lvar_scope !curr_fun_id id (List.hd ctx)

let debug_scope ctx =
  debug_annot 6L (empty_string :: List.rev_map integer_const ctx)

(* Add line number debug annotation if the line number changes.
   Labels are ignored since the code before the label can become
   unreachable. Add scope debug annotation regardless. *)


let add_lineno ?(label=false) ctx prev_loc this_loc s =
  if !Clflags.option_g then
    sseq no_loc (debug_scope ctx)
      (if this_loc <> prev_loc && this_loc <> no_loc && not label
       then sseq no_loc (debug_lineno this_loc) s
       else s)
  else s

(* Generate fresh scope identifiers, for blocks that contain at least
   one declaration *)

let block_contains_decl sl =
  List.exists
    (function {sdesc = Sdecl _} -> true | _ -> false)
    sl

let next_scope_id = ref 0

let new_scope_id () =
  incr next_scope_id; !next_scope_id

(* Process a block-scoped variable declaration.
   The variable is entered in [local_variables].
   The initializer, if any, is converted into assignments and
   prepended to [k]. *)

let process_decl loc env ctx (sto, id, ty, optinit) k =
  let ty' = remove_const env ty in
  local_variables := (sto, id, ty', None) :: !local_variables;
  debug_var_decl ctx id;
  (* TODO: register the fact that id is declared in scope ctx *)
  match optinit with
  | None ->
      k
  | Some init ->
      let init' = expand_init true env init in
      let l = local_initializer env { edesc = EVar id; etyp = ty' } init' [] in
      add_inits_stmt loc l k

(* Simplification of blocks within a statement *)

let rec unblock_stmt env ctx ploc s =
  match s.sdesc with
  | Sskip -> s
  | Sdo e ->
      add_lineno ctx ploc s.sloc
        {s with sdesc = Sdo(expand_expr true env e)}
  | Sseq(s1, s2) ->
      {s with sdesc = Sseq(unblock_stmt env ctx ploc s1,
                           unblock_stmt env ctx s1.sloc s2)}
  | Sif(e, s1, s2) ->
      add_lineno ctx ploc s.sloc
        {s with sdesc = Sif(expand_expr true env e,
                            unblock_stmt env ctx s.sloc s1,
                            unblock_stmt env ctx s.sloc s2)}
  | Swhile(e, s1) ->
      add_lineno ctx ploc s.sloc
        {s with sdesc = Swhile(expand_expr true env e,
                               unblock_stmt env ctx s.sloc s1)}
  | Sdowhile(s1, e) ->
      add_lineno ctx ploc s.sloc
        {s with sdesc = Sdowhile(unblock_stmt env ctx s.sloc s1,
                                 expand_expr true env e)}
  | Sfor(s1, e, s2, s3) ->
      add_lineno ctx ploc s.sloc
        {s with sdesc = Sfor(unblock_stmt env ctx s.sloc s1,
                             expand_expr true env e,
                             unblock_stmt env ctx s.sloc s2,
                             unblock_stmt env ctx s.sloc s3)}
  | Sbreak -> s
  | Scontinue -> s
  | Sswitch(e, s1) ->
      add_lineno ctx ploc s.sloc
        {s with sdesc = Sswitch(expand_expr true env e,
                                unblock_stmt env ctx s.sloc s1)}
  | Slabeled(lbl, s1) ->
    let loc,label = if s.sloc <> s1.sloc then
        s.sloc,false (* Label and code are on different lines *)
      else
        ploc,true in
    add_lineno ~label:label ctx ploc s.sloc
        {s with sdesc = Slabeled(lbl, unblock_stmt env ctx loc s1)}
  | Sgoto lbl ->
      add_lineno ctx ploc s.sloc s
  | Sreturn None ->
      add_lineno ctx ploc s.sloc s
  | Sreturn (Some e) ->
      add_lineno ctx ploc s.sloc
        {s with sdesc = Sreturn(Some (expand_expr true env e))}
  | Sblock sl ->
      let ctx' =
        if block_contains_decl sl
        then
          let id = new_scope_id () in
          (match ctx with
          | [] -> Debug.enter_function_scope !curr_fun_id id
          | a::_ -> Debug.enter_scope !curr_fun_id a id);
          id:: ctx
        else ctx in
      unblock_block env ctx' ploc sl
  | Sdecl d ->
      assert false
  | Sasm(attr, template, outputs, inputs, clob) ->
      let expand_asm_operand (lbl, cstr, e) =
        (lbl, cstr, expand_expr true env e) in
      add_lineno ctx ploc s.sloc
        {s with sdesc = Sasm(attr, template,
                             List.map expand_asm_operand outputs,
                             List.map expand_asm_operand inputs, clob)}


and unblock_block env ctx ploc = function
  | [] -> sskip
  | {sdesc = Sdecl d; sloc = loc} :: sl ->
      add_lineno ctx ploc loc
        (process_decl loc env ctx d
           (unblock_block env ctx loc sl))
  | s :: sl ->
      sseq s.sloc (unblock_stmt env ctx ploc s)
                  (unblock_block env ctx s.sloc sl)

(* Simplification of blocks and compound literals within a function *)

let unblock_fundef env f =
  local_variables := [];
  curr_fun_id:= f.fd_name.stamp;
  (* TODO: register the parameters as being declared in function scope *)
  let body = unblock_stmt env [] no_loc f.fd_body in
  let decls = !local_variables in
  local_variables := [];
  { f with fd_locals = f.fd_locals @ decls; fd_body = body }

(* Simplification of compound literals within a top-level declaration *)

let unblock_decl env ((sto, id, ty, optinit) as d) =
  match optinit with
  | None -> [d]
  | Some init ->
      global_variables := [];
      let init' = expand_init false env init in
      let decls = !global_variables in
      global_variables := [];
      decls @ [(sto, id, ty, Some init')]

(* Unblocking and simplification for whole files.
   The environment is used for typedefs and composites only,
   so we do not maintain variable and function definitions. *)

let rec unblock_glob env accu = function
  | [] -> List.rev accu
  | g :: gl ->
      match g.gdesc with
      | Gdecl d ->
          let dl = unblock_decl env d in
          unblock_glob env
            (List.rev_append
               (List.map (fun d' -> {g with gdesc = Gdecl d'}) dl)
               accu)
            gl
      | Gfundef f ->
          let f' = unblock_fundef env f in
          unblock_glob env ({g with gdesc = Gfundef f'} :: accu) gl
      | Gcompositedecl(su, id, attr) ->
          unblock_glob
            (Env.add_composite env id (composite_info_decl su attr))
            (g :: accu) gl
      | Gcompositedef(su, id, attr, fl) ->
          unblock_glob
            (Env.add_composite env id (composite_info_def env su attr fl))
            (g :: accu) gl
      | Gtypedef(id, ty) ->
          unblock_glob (Env.add_typedef env id ty) (g :: accu) gl
      | Genumdef (id, attr, members) ->
          unblock_glob
            (Env.add_enum env id {Env.ei_members =  members; Env.ei_attr = attr})
            (g :: accu) gl
      | Gpragma _ ->
          unblock_glob env (g :: accu) gl

(* Entry point *)

let program p =
  next_scope_id := 0;
  {gloc = no_loc; gdesc = Gdecl(Storage_extern, debug_id, debug_ty, None)} ::
  unblock_glob (Env.initial()) [] p