--- /dev/null
+/* This file is part of the program psim.
+
+ Copyright (C) 1994-1998, Andrew Cagney <cagney@highland.com.au>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+ */
+
+
+#include "misc.h"
+#include "lf.h"
+#include "table.h"
+#include "filter.h"
+#include "igen.h"
+
+#include "ld-insn.h"
+#include "ld-decode.h"
+
+#include "gen.h"
+
+#include "gen-semantics.h"
+#include "gen-idecode.h"
+#include "gen-icache.h"
+
+
+
+static void
+print_icache_function_header (lf *file,
+ const char *basename,
+ const char *format_name,
+ opcode_bits *expanded_bits,
+ int is_function_definition,
+ int nr_prefetched_words)
+{
+ lf_printf(file, "\n");
+ lf_print__function_type_function (file, print_icache_function_type,
+ "EXTERN_ICACHE", " ");
+ print_function_name (file,
+ basename, format_name, NULL,
+ expanded_bits,
+ function_name_prefix_icache);
+ lf_printf (file, "\n(");
+ print_icache_function_formal (file, nr_prefetched_words);
+ lf_printf (file, ")");
+ if (!is_function_definition)
+ lf_printf (file, ";");
+ lf_printf (file, "\n");
+}
+
+
+void
+print_icache_declaration (lf *file,
+ insn_entry *insn,
+ opcode_bits *expanded_bits,
+ insn_opcodes *opcodes,
+ int nr_prefetched_words)
+{
+ print_icache_function_header (file,
+ insn->name,
+ insn->format_name,
+ expanded_bits,
+ 0/* is not function definition */,
+ nr_prefetched_words);
+}
+
+
+
+static void
+print_icache_extraction (lf *file,
+ const char *format_name,
+ cache_entry_type cache_type,
+ const char *entry_name,
+ const char *entry_type,
+ const char *entry_expression,
+ char *single_insn_field,
+ line_ref *line,
+ insn_field_entry *cur_field,
+ opcode_bits *expanded_bits,
+ icache_decl_type what_to_declare,
+ icache_body_type what_to_do)
+{
+ const char *expression;
+ opcode_bits *bits;
+ char *reason;
+ ASSERT (format_name != NULL);
+ ASSERT (entry_name != NULL);
+
+ /* figure out exactly what should be going on here */
+ switch (cache_type)
+ {
+ case scratch_value:
+ if ((what_to_do & put_values_in_icache)
+ || what_to_do == do_not_use_icache)
+ {
+ reason = "scratch";
+ what_to_do = do_not_use_icache;
+ }
+ else
+ return;
+ break;
+ case compute_value:
+ if ((what_to_do & get_values_from_icache)
+ || what_to_do == do_not_use_icache)
+ {
+ reason = "compute";
+ what_to_do = do_not_use_icache;
+ }
+ else
+ return;
+ break;
+ case cache_value:
+ if ((what_to_declare != undef_variables)
+ || !(what_to_do & put_values_in_icache))
+ {
+ reason = "cache";
+ what_to_declare = ((what_to_do & put_values_in_icache)
+ ? declare_variables
+ : what_to_declare);
+ }
+ else
+ return;
+ break;
+ }
+
+ /* For the type, default to a simple unsigned */
+ if (entry_type == NULL || strlen (entry_type) == 0)
+ entry_type = "unsigned";
+
+ /* look through the set of expanded sub fields to see if this field
+ has been given a constant value */
+ for (bits = expanded_bits;
+ bits != NULL;
+ bits = bits->next)
+ {
+ if (bits->field == cur_field)
+ break;
+ }
+
+ /* Define a storage area for the cache element */
+ switch (what_to_declare)
+ {
+ case undef_variables:
+ /* We've finished with the #define value - destory it */
+ lf_indent_suppress (file);
+ lf_printf (file, "#undef %s\n", entry_name);
+ return;
+ case define_variables:
+ /* Using direct access for this entry, define it */
+ lf_indent_suppress (file);
+ lf_printf (file, "#define %s ((%s) ", entry_name, entry_type);
+ break;
+ case declare_variables:
+ /* using variables to define the value */
+ if (line != NULL)
+ lf_print__line_ref (file, line);
+ lf_printf (file, "%s const %s UNUSED = ", entry_type, entry_name);
+ break;
+ }
+
+
+ /* define a value for that storage area as determined by what is in
+ the cache */
+ if (bits != NULL
+ && single_insn_field != NULL
+ && strcmp (entry_name, single_insn_field) == 0
+ && strcmp (entry_name, cur_field->val_string) == 0
+ && ((bits->opcode->is_boolean && bits->value == 0)
+ || (!bits->opcode->is_boolean)))
+ {
+ /* The cache rule is specifying what to do with a simple
+ instruction field.
+
+ Because of instruction expansion, the field is either a
+ constant value or equal to the specified constant (boolean
+ comparison). (The latter indicated by bits->value == 0).
+
+ The case of a field not being equal to the specified boolean
+ value is handled later. */
+ expression = "constant field";
+ ASSERT (bits->field == cur_field);
+ if (bits->opcode->is_boolean)
+ {
+ ASSERT (bits->value == 0);
+ lf_printf (file, "%d", bits->opcode->boolean_constant);
+ }
+ else if (bits->opcode->last < bits->field->last)
+ {
+ lf_printf (file, "%d",
+ bits->value << (bits->field->last - bits->opcode->last));
+ }
+ else
+ {
+ lf_printf (file, "%d", bits->value);
+ }
+ }
+ else if (bits != NULL
+ && single_insn_field != NULL
+ && strncmp (entry_name,
+ single_insn_field,
+ strlen (single_insn_field)) == 0
+ && strncmp (entry_name + strlen (single_insn_field),
+ "_is_",
+ strlen ("_is_")) == 0
+ && ((bits->opcode->is_boolean
+ && ((unsigned) atol (entry_name + strlen (single_insn_field) + strlen ("_is_"))
+ == bits->opcode->boolean_constant))
+ || (!bits->opcode->is_boolean)))
+ {
+ /* The cache rule defines an entry for the comparison between a
+ single instruction field and a constant. The value of the
+ comparison in someway matches that of the opcode field that
+ was made constant through expansion. */
+ expression = "constant compare";
+ if (bits->opcode->is_boolean)
+ {
+ lf_printf (file, "%d /* %s == %d */",
+ bits->value == 0,
+ single_insn_field,
+ bits->opcode->boolean_constant);
+ }
+ else if (bits->opcode->last < bits->field->last)
+ {
+ lf_printf (file, "%d /* %s == %d */",
+ (atol (entry_name + strlen (single_insn_field) + strlen ("_is_"))
+ == (bits->value << (bits->field->last - bits->opcode->last))),
+ single_insn_field,
+ (bits->value << (bits->field->last - bits->opcode->last)));
+ }
+ else
+ {
+ lf_printf (file, "%d /* %s == %d */",
+ (atol (entry_name + strlen (single_insn_field) + strlen ("_is_"))
+ == bits->value),
+ single_insn_field,
+ bits->value);
+ }
+ }
+ else
+ {
+ /* put the field in the local variable, possibly also enter it
+ into the cache */
+ expression = "extraction";
+ /* handle the cache */
+ if ((what_to_do & get_values_from_icache)
+ || (what_to_do & put_values_in_icache))
+ {
+ lf_printf (file, "cache_entry->crack.%s.%s",
+ format_name,
+ entry_name);
+ if (what_to_do & put_values_in_icache) /* also put it in the cache? */
+ {
+ lf_printf (file, " = ");
+ }
+ }
+ if ((what_to_do & put_values_in_icache)
+ || what_to_do == do_not_use_icache)
+ {
+ if (cur_field != NULL)
+ {
+ if (entry_expression != NULL && strlen (entry_expression) > 0)
+ error (line, "Instruction field entry with nonempty expression\n");
+ if (cur_field->first == 0 && cur_field->last == options.insn_bit_size - 1)
+ lf_printf (file, "(instruction_%d)",
+ cur_field->word_nr);
+ else if (cur_field->last == options.insn_bit_size - 1)
+ lf_printf (file, "MASKED%d (instruction_%d, %d, %d)",
+ options.insn_bit_size,
+ cur_field->word_nr,
+ i2target (options.hi_bit_nr, cur_field->first),
+ i2target (options.hi_bit_nr, cur_field->last));
+ else
+ lf_printf (file, "EXTRACTED%d (instruction_%d, %d, %d)",
+ options.insn_bit_size,
+ cur_field->word_nr,
+ i2target (options.hi_bit_nr, cur_field->first),
+ i2target (options.hi_bit_nr, cur_field->last));
+ }
+ else
+ {
+ lf_printf (file, "%s", entry_expression);
+ }
+ }
+ }
+
+ switch (what_to_declare)
+ {
+ case define_variables:
+ lf_printf (file, ")");
+ break;
+ case undef_variables:
+ break;
+ case declare_variables:
+ lf_printf (file, ";");
+ break;
+ }
+
+ ASSERT (reason != NULL && expression != NULL);
+ lf_printf (file, " /* %s - %s */\n", reason, expression);
+}
+
+
+void
+print_icache_body (lf *file,
+ insn_entry *instruction,
+ opcode_bits *expanded_bits,
+ cache_entry *cache_rules,
+ icache_decl_type what_to_declare,
+ icache_body_type what_to_do,
+ int nr_prefetched_words)
+{
+ /* extract instruction fields */
+ lf_printf (file, "/* Extraction: %s\n", instruction->name);
+ lf_printf (file, " ");
+ switch (what_to_declare)
+ {
+ case define_variables:
+ lf_printf (file, "#define");
+ break;
+ case declare_variables:
+ lf_printf (file, "declare");
+ break;
+ case undef_variables:
+ lf_printf (file, "#undef");
+ break;
+ }
+ lf_printf (file, " ");
+ switch (what_to_do)
+ {
+ case get_values_from_icache:
+ lf_printf (file, "get-values-from-icache");
+ break;
+ case put_values_in_icache:
+ lf_printf (file, "put-values-in-icache");
+ break;
+ case both_values_and_icache:
+ lf_printf (file, "get-values-from-icache|put-values-in-icache");
+ break;
+ case do_not_use_icache:
+ lf_printf (file, "do-not-use-icache");
+ break;
+ }
+ lf_printf (file, "\n ");
+ print_insn_words (file, instruction);
+ lf_printf(file, " */\n");
+
+ /* pass zero - fetch from memory any missing instructions.
+
+ Some of the instructions will have already been fetched (in the
+ instruction array), others will still need fetching. */
+ switch (what_to_do)
+ {
+ case get_values_from_icache:
+ break;
+ case put_values_in_icache:
+ case both_values_and_icache:
+ case do_not_use_icache:
+ {
+ int word_nr;
+ switch (what_to_declare)
+ {
+ case undef_variables:
+ break;
+ case define_variables:
+ case declare_variables:
+ for (word_nr = nr_prefetched_words;
+ word_nr < instruction->nr_words;
+ word_nr++)
+ {
+ /* FIXME - should be using print_icache_extraction? */
+ lf_printf (file, "%sinstruction_word instruction_%d UNUSED = ",
+ options.module.global.prefix.l,
+ word_nr);
+ lf_printf (file, "IMEM%d_IMMED (cia, %d)",
+ options.insn_bit_size, word_nr);
+ lf_printf (file, ";\n");
+ }
+ }
+ }
+ }
+
+ /* if putting the instruction words in the cache, define references
+ for them */
+ if (options.gen.insn_in_icache) {
+ /* FIXME: is the instruction_word type correct? */
+ print_icache_extraction (file,
+ instruction->format_name,
+ cache_value,
+ "insn", /* name */
+ "instruction_word", /* type */
+ "instruction", /* expression */
+ NULL, /* origin */
+ NULL, /* line */
+ NULL, NULL,
+ what_to_declare,
+ what_to_do);
+ }
+ lf_printf(file, "\n");
+
+ /* pass one - process instruction fields.
+
+ If there is no cache rule, the default is to enter the field into
+ the cache */
+ {
+ insn_word_entry *word;
+ for (word = instruction->words;
+ word != NULL;
+ word = word->next)
+ {
+ insn_field_entry *cur_field;
+ for (cur_field = word->first;
+ cur_field->first < options.insn_bit_size;
+ cur_field = cur_field->next)
+ {
+ if (cur_field->type == insn_field_string)
+ {
+ cache_entry *cache_rule;
+ cache_entry_type value_type = cache_value;
+ line_ref *value_line = instruction->line;
+ /* check the cache table to see if it contains a rule
+ overriding the default cache action for an
+ instruction field */
+ for (cache_rule = cache_rules;
+ cache_rule != NULL;
+ cache_rule = cache_rule->next)
+ {
+ if (filter_is_subset (instruction->field_names,
+ cache_rule->original_fields)
+ && strcmp (cache_rule->name, cur_field->val_string) == 0)
+ {
+ value_type = cache_rule->entry_type;
+ value_line = cache_rule->line;
+ if (value_type == compute_value)
+ {
+ options.warning (cache_rule->line,
+ "instruction field of type `compute' changed to `cache'\n");
+ cache_rule->entry_type = cache_value;
+ }
+ break;
+ }
+ }
+ /* Define an entry for the field within the
+ instruction */
+ print_icache_extraction (file,
+ instruction->format_name,
+ value_type,
+ cur_field->val_string, /* name */
+ NULL, /* type */
+ NULL, /* expression */
+ cur_field->val_string, /* insn field */
+ value_line,
+ cur_field,
+ expanded_bits,
+ what_to_declare,
+ what_to_do);
+ }
+ }
+ }
+ }
+
+ /* pass two - any cache fields not processed above */
+ {
+ cache_entry *cache_rule;
+ for (cache_rule = cache_rules;
+ cache_rule != NULL;
+ cache_rule = cache_rule->next)
+ {
+ if (filter_is_subset (instruction->field_names,
+ cache_rule->original_fields)
+ && !filter_is_member (instruction->field_names,
+ cache_rule->name))
+ {
+ char *single_field = filter_next (cache_rule->original_fields, "");
+ if (filter_next (cache_rule->original_fields, single_field) != NULL)
+ single_field = NULL;
+ print_icache_extraction (file,
+ instruction->format_name,
+ cache_rule->entry_type,
+ cache_rule->name,
+ cache_rule->type,
+ cache_rule->expression,
+ single_field,
+ cache_rule->line,
+ NULL, /* cur_field */
+ expanded_bits,
+ what_to_declare,
+ what_to_do);
+ }
+ }
+ }
+
+ lf_print__internal_ref (file);
+}
+
+
+
+typedef struct _form_fields form_fields;
+struct _form_fields {
+ char *name;
+ filter *fields;
+ form_fields *next;
+};
+
+static form_fields *
+insn_table_cache_fields (insn_table *isa)
+{
+ form_fields *forms = NULL;
+ insn_entry *insn;
+ for (insn = isa->insns;
+ insn != NULL;
+ insn = insn->next) {
+ form_fields **form = &forms;
+ while (1)
+ {
+ if (*form == NULL)
+ {
+ /* new format name, add it */
+ form_fields *new_form = ZALLOC (form_fields);
+ new_form->name = insn->format_name;
+ filter_add (&new_form->fields, insn->field_names);
+ *form = new_form;
+ break;
+ }
+ else if (strcmp ((*form)->name, insn->format_name) == 0)
+ {
+ /* already present, add field names to the existing list */
+ filter_add (&(*form)->fields, insn->field_names);
+ break;
+ }
+ form = &(*form)->next;
+ }
+ }
+ return forms;
+}
+
+
+
+extern void
+print_icache_struct (lf *file,
+ insn_table *isa,
+ cache_entry *cache_rules)
+{
+ /* Create a list of all the different instruction formats with their
+ corresponding field names. */
+ form_fields *formats = insn_table_cache_fields (isa);
+
+ lf_printf (file, "\n");
+ lf_printf (file, "#define WITH_%sIDECODE_CACHE_SIZE %d\n",
+ options.module.global.prefix.u,
+ (options.gen.icache ? options.gen.icache_size : 0));
+ lf_printf (file, "\n");
+
+ /* create an instruction cache if being used */
+ if (options.gen.icache) {
+ lf_printf (file, "typedef struct _%sidecode_cache {\n",
+ options.module.global.prefix.l);
+ lf_indent (file, +2);
+ {
+ form_fields *format;
+ lf_printf (file, "unsigned_word address;\n");
+ lf_printf (file, "void *semantic;\n");
+ lf_printf (file, "union {\n");
+ lf_indent (file, +2);
+ for (format = formats;
+ format != NULL;
+ format = format->next)
+ {
+ lf_printf (file, "struct {\n");
+ lf_indent (file, +2);
+ {
+ cache_entry *cache_rule;
+ char *field;
+ /* space for any instruction words */
+ if (options.gen.insn_in_icache)
+ lf_printf (file, "instruction_word insn[%d];\n", isa->max_nr_words);
+ /* define an entry for any applicable cache rules */
+ for (cache_rule = cache_rules;
+ cache_rule != NULL;
+ cache_rule = cache_rule->next)
+ {
+ /* nb - sort of correct - should really check against
+ individual instructions */
+ if (filter_is_subset (format->fields, cache_rule->original_fields))
+ {
+ char *memb;
+ lf_printf (file, "%s %s;",
+ (cache_rule->type == NULL
+ ? "unsigned"
+ : cache_rule->type),
+ cache_rule->name);
+ lf_printf (file, " /*");
+ for (memb = filter_next (cache_rule->original_fields, "");
+ memb != NULL;
+ memb = filter_next (cache_rule->original_fields, memb))
+ {
+ lf_printf (file, " %s", memb);
+ }
+ lf_printf (file, " */\n");
+ }
+ }
+ /* define an entry for any fields not covered by a cache rule */
+ for (field = filter_next (format->fields, "");
+ field != NULL;
+ field = filter_next (format->fields, field))
+ {
+ cache_entry *cache_rule;
+ int found_rule = 0;
+ for (cache_rule = cache_rules;
+ cache_rule != NULL;
+ cache_rule = cache_rule->next)
+ {
+ if (strcmp (cache_rule->name, field) == 0)
+ {
+ found_rule = 1;
+ break;
+ }
+ }
+ if (!found_rule)
+ lf_printf (file, "unsigned %s; /* default */\n", field);
+ }
+ }
+ lf_indent (file, -2);
+ lf_printf (file, "} %s;\n", format->name);
+ }
+ lf_indent (file, -2);
+ lf_printf (file, "} crack;\n");
+ }
+ lf_indent (file, -2);
+ lf_printf (file, "} %sidecode_cache;\n", options.module.global.prefix.l);
+ }
+ else
+ {
+ /* alernativly, since no cache, emit a dummy definition for
+ idecode_cache so that code refering to the type can still compile */
+ lf_printf(file, "typedef void %sidecode_cache;\n",
+ options.module.global.prefix.l);
+ }
+ lf_printf (file, "\n");
+}
+
+
+
+static void
+print_icache_function (lf *file,
+ insn_entry *instruction,
+ opcode_bits *expanded_bits,
+ insn_opcodes *opcodes,
+ cache_entry *cache_rules,
+ int nr_prefetched_words)
+{
+ int indent;
+
+ /* generate code to enter decoded instruction into the icache */
+ lf_printf(file, "\n");
+ lf_print__function_type_function (file, print_icache_function_type,
+ "EXTERN_ICACHE", "\n");
+ indent = print_function_name (file,
+ instruction->name,
+ instruction->format_name,
+ NULL,
+ expanded_bits,
+ function_name_prefix_icache);
+ indent += lf_printf (file, " ");
+ lf_indent (file, +indent);
+ lf_printf (file, "(");
+ print_icache_function_formal (file, nr_prefetched_words);
+ lf_printf (file, ")\n");
+ lf_indent (file, -indent);
+
+ /* function header */
+ lf_printf (file, "{\n");
+ lf_indent (file, +2);
+
+ print_my_defines (file,
+ instruction->name,
+ instruction->format_name,
+ expanded_bits);
+ print_itrace (file, instruction, 1/*putting-value-in-cache*/);
+
+ print_idecode_validate (file, instruction, opcodes);
+
+ lf_printf (file, "\n");
+ lf_printf (file, "{\n");
+ lf_indent (file, +2);
+ if (options.gen.semantic_icache)
+ lf_printf (file, "unsigned_word nia;\n");
+ print_icache_body (file,
+ instruction,
+ expanded_bits,
+ cache_rules,
+ (options.gen.direct_access
+ ? define_variables
+ : declare_variables),
+ (options.gen.semantic_icache
+ ? both_values_and_icache
+ : put_values_in_icache),
+ nr_prefetched_words);
+
+ lf_printf (file, "\n");
+ lf_printf (file, "cache_entry->address = cia;\n");
+ lf_printf (file, "cache_entry->semantic = ");
+ print_function_name (file,
+ instruction->name,
+ instruction->format_name,
+ NULL,
+ expanded_bits,
+ function_name_prefix_semantics);
+ lf_printf (file, ";\n");
+ lf_printf (file, "\n");
+
+ if (options.gen.semantic_icache) {
+ lf_printf (file, "/* semantic routine */\n");
+ print_semantic_body (file,
+ instruction,
+ expanded_bits,
+ opcodes);
+ lf_printf (file, "return nia;\n");
+ }
+
+ if (!options.gen.semantic_icache)
+ {
+ lf_printf (file, "/* return the function proper */\n");
+ lf_printf (file, "return ");
+ print_function_name (file,
+ instruction->name,
+ instruction->format_name,
+ NULL,
+ expanded_bits,
+ function_name_prefix_semantics);
+ lf_printf (file, ";\n");
+ }
+
+ if (options.gen.direct_access)
+ {
+ print_icache_body (file,
+ instruction,
+ expanded_bits,
+ cache_rules,
+ undef_variables,
+ (options.gen.semantic_icache
+ ? both_values_and_icache
+ : put_values_in_icache),
+ nr_prefetched_words);
+ }
+
+ lf_indent (file, -2);
+ lf_printf (file, "}\n");
+ lf_indent (file, -2);
+ lf_printf (file, "}\n");
+}
+
+
+void
+print_icache_definition (lf *file,
+ insn_entry *insn,
+ opcode_bits *expanded_bits,
+ insn_opcodes *opcodes,
+ cache_entry *cache_rules,
+ int nr_prefetched_words)
+{
+ print_icache_function (file,
+ insn,
+ expanded_bits,
+ opcodes,
+ cache_rules,
+ nr_prefetched_words);
+}
+
+
+
+void
+print_icache_internal_function_declaration (lf *file,
+ function_entry *function,
+ void *data)
+{
+ ASSERT (options.gen.icache);
+ if (function->is_internal)
+ {
+ lf_printf (file, "\n");
+ lf_print__function_type_function (file, print_icache_function_type,
+ "INLINE_ICACHE", "\n");
+ print_function_name (file,
+ function->name,
+ NULL,
+ NULL,
+ NULL,
+ function_name_prefix_icache);
+ lf_printf (file, "\n(");
+ print_icache_function_formal (file, 0);
+ lf_printf (file, ");\n");
+ }
+}
+
+
+void
+print_icache_internal_function_definition (lf *file,
+ function_entry *function,
+ void *data)
+{
+ ASSERT (options.gen.icache);
+ if (function->is_internal)
+ {
+ lf_printf (file, "\n");
+ lf_print__function_type_function (file, print_icache_function_type,
+ "INLINE_ICACHE", "\n");
+ print_function_name (file,
+ function->name,
+ NULL,
+ NULL,
+ NULL,
+ function_name_prefix_icache);
+ lf_printf (file, "\n(");
+ print_icache_function_formal (file, 0);
+ lf_printf (file, ")\n");
+ lf_printf (file, "{\n");
+ lf_indent (file, +2);
+ lf_printf (file, "/* semantic routine */\n");
+ if (options.gen.semantic_icache)
+ {
+ lf_print__line_ref (file, function->code->line);
+ table_print_code (file, function->code);
+ lf_printf (file, "error (\"Internal function must longjump\\n\");\n");
+ lf_printf (file, "return 0;\n");
+ }
+ else
+ {
+ lf_printf (file, "return ");
+ print_function_name (file,
+ function->name,
+ NULL,
+ NULL,
+ NULL,
+ function_name_prefix_semantics);
+ lf_printf (file, ";\n");
+ }
+
+ lf_print__internal_ref (file);
+ lf_indent (file, -2);
+ lf_printf (file, "}\n");
+ }
+}