1/**************************************************************************
2 *
3 * Copyright 2008 VMware, Inc.
4 * All Rights Reserved.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sub license, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
13 *
14 * The above copyright notice and this permission notice (including the
15 * next paragraph) shall be included in all copies or substantial portions
16 * of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
21 * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR
22 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 *
26 **************************************************************************/
27
28/**
29 * TGSI program transformation utility.
30 *
31 * Authors:  Brian Paul
32 */
33
34#include "util/u_debug.h"
35#include "util/log.h"
36
37#include "tgsi_transform.h"
38
39/**
40 * Increments the next-token index if the tgsi_build_* succeeded, or extends the
41 * token array and returns true to request a re-emit of the tgsi_build_* by the
42 * caller.
43 */
44static bool
45need_re_emit(struct tgsi_transform_context *ctx, uint32_t emitted, struct tgsi_header orig_header)
46{
47   if (emitted > 0) {
48      ctx->ti += emitted;
49      return false;
50   } else {
51      uint32_t new_len = ctx->max_tokens_out * 2;
52      if (new_len < ctx->max_tokens_out) {
53         ctx->fail = true;
54         return false;
55      }
56
57      struct tgsi_token *new_tokens = tgsi_alloc_tokens(new_len);
58      if (!new_tokens) {
59         ctx->fail = true;
60         return false;
61      }
62      memcpy(new_tokens, ctx->tokens_out, sizeof(struct tgsi_token) * ctx->ti);
63
64      tgsi_free_tokens(ctx->tokens_out);
65      ctx->tokens_out = new_tokens;
66      ctx->max_tokens_out = new_len;
67
68      /* Point the header at the resized tokens. */
69      ctx->header = (struct tgsi_header *)new_tokens;
70      /* The failing emit may have incremented header/body size, reset it to its state before our attempt. */
71      *ctx->header = orig_header;
72
73      return true;
74   }
75}
76
77static void
78emit_instruction(struct tgsi_transform_context *ctx,
79                 const struct tgsi_full_instruction *inst)
80{
81   uint32_t emitted;
82   struct tgsi_header orig_header = *ctx->header;
83
84   do {
85      emitted = tgsi_build_full_instruction(inst,
86                                            ctx->tokens_out + ctx->ti,
87                                            ctx->header,
88                                            ctx->max_tokens_out - ctx->ti);
89   } while (need_re_emit(ctx, emitted, orig_header));
90}
91
92
93static void
94emit_declaration(struct tgsi_transform_context *ctx,
95                 const struct tgsi_full_declaration *decl)
96{
97   uint32_t emitted;
98   struct tgsi_header orig_header = *ctx->header;
99
100   do {
101      emitted = tgsi_build_full_declaration(decl,
102                                            ctx->tokens_out + ctx->ti,
103                                            ctx->header,
104                                            ctx->max_tokens_out - ctx->ti);
105   } while (need_re_emit(ctx, emitted, orig_header));
106}
107
108
109static void
110emit_immediate(struct tgsi_transform_context *ctx,
111               const struct tgsi_full_immediate *imm)
112{
113   uint32_t emitted;
114   struct tgsi_header orig_header = *ctx->header;
115
116   do {
117      emitted = tgsi_build_full_immediate(imm,
118                                          ctx->tokens_out + ctx->ti,
119                                          ctx->header,
120                                          ctx->max_tokens_out - ctx->ti);
121   } while (need_re_emit(ctx, emitted, orig_header));
122}
123
124
125static void
126emit_property(struct tgsi_transform_context *ctx,
127              const struct tgsi_full_property *prop)
128{
129   uint32_t emitted;
130   struct tgsi_header orig_header = *ctx->header;
131
132   do {
133      emitted = tgsi_build_full_property(prop,
134                                         ctx->tokens_out + ctx->ti,
135                                         ctx->header,
136                                         ctx->max_tokens_out - ctx->ti);
137   } while (need_re_emit(ctx, emitted, orig_header));
138}
139
140
141/**
142 * Apply user-defined transformations to the input shader to produce
143 * the output shader.
144 * For example, a register search-and-replace operation could be applied
145 * by defining a transform_instruction() callback that examined and changed
146 * the instruction src/dest regs.
147 *
148 * \return new tgsi tokens, or NULL on failure
149 */
150struct tgsi_token *
151tgsi_transform_shader(const struct tgsi_token *tokens_in,
152                      uint initial_tokens_len,
153                      struct tgsi_transform_context *ctx)
154{
155   boolean first_instruction = TRUE;
156   boolean epilog_emitted = FALSE;
157   int cond_stack = 0;
158   int call_stack = 0;
159
160   /* input shader */
161   struct tgsi_parse_context parse;
162
163   /* output shader */
164   struct tgsi_processor *processor;
165
166   /* Always include space for the header. */
167   initial_tokens_len = MAX2(initial_tokens_len, 2);
168
169   /**
170    ** callback context init
171    **/
172   ctx->emit_instruction = emit_instruction;
173   ctx->emit_declaration = emit_declaration;
174   ctx->emit_immediate = emit_immediate;
175   ctx->emit_property = emit_property;
176   ctx->tokens_out = tgsi_alloc_tokens(initial_tokens_len);
177   ctx->max_tokens_out = initial_tokens_len;
178   ctx->fail = false;
179
180   if (!ctx->tokens_out) {
181      mesa_loge("failed to allocate %d tokens\n", initial_tokens_len);
182      return NULL;
183   }
184
185   /**
186    ** Setup to begin parsing input shader
187    **/
188   if (tgsi_parse_init( &parse, tokens_in ) != TGSI_PARSE_OK) {
189      debug_printf("tgsi_parse_init() failed in tgsi_transform_shader()!\n");
190      return NULL;
191   }
192   ctx->processor = parse.FullHeader.Processor.Processor;
193
194   /**
195    **  Setup output shader
196    **/
197   ctx->header = (struct tgsi_header *)ctx->tokens_out;
198   *ctx->header = tgsi_build_header();
199
200   processor = (struct tgsi_processor *) (ctx->tokens_out + 1);
201   *processor = tgsi_build_processor( ctx->processor, ctx->header );
202
203   ctx->ti = 2;
204
205
206   /**
207    ** Loop over incoming program tokens/instructions
208    */
209   while( !tgsi_parse_end_of_tokens( &parse ) ) {
210
211      tgsi_parse_token( &parse );
212
213      switch( parse.FullToken.Token.Type ) {
214      case TGSI_TOKEN_TYPE_INSTRUCTION:
215         {
216            struct tgsi_full_instruction *fullinst
217               = &parse.FullToken.FullInstruction;
218            enum tgsi_opcode opcode = fullinst->Instruction.Opcode;
219
220            if (first_instruction && ctx->prolog) {
221               ctx->prolog(ctx);
222            }
223
224            /*
225             * XXX Note: we handle the case of ret in main.
226             * However, the output redirections done by transform
227             * have their limits with control flow and will generally
228             * not work correctly. e.g.
229             * if (cond) {
230             *    oColor = x;
231             *    ret;
232             * }
233             * oColor = y;
234             * end;
235             * If the color output is redirected to a temp and modified
236             * by a transform, this will not work (the oColor assignment
237             * in the conditional will never make it to the actual output).
238             */
239            if ((opcode == TGSI_OPCODE_END || opcode == TGSI_OPCODE_RET) &&
240                 call_stack == 0 && ctx->epilog && !epilog_emitted) {
241               if (opcode == TGSI_OPCODE_RET && cond_stack != 0) {
242                  assert(!"transform ignoring RET in main");
243               } else {
244                  assert(cond_stack == 0);
245                  /* Emit caller's epilog */
246                  ctx->epilog(ctx);
247                  epilog_emitted = TRUE;
248               }
249               /* Emit END (or RET) */
250               ctx->emit_instruction(ctx, fullinst);
251            }
252            else {
253               switch (opcode) {
254               case TGSI_OPCODE_IF:
255               case TGSI_OPCODE_UIF:
256               case TGSI_OPCODE_SWITCH:
257               case TGSI_OPCODE_BGNLOOP:
258                  cond_stack++;
259                  break;
260               case TGSI_OPCODE_CAL:
261                  call_stack++;
262                  break;
263               case TGSI_OPCODE_ENDIF:
264               case TGSI_OPCODE_ENDSWITCH:
265               case TGSI_OPCODE_ENDLOOP:
266                  assert(cond_stack > 0);
267                  cond_stack--;
268                  break;
269               case TGSI_OPCODE_ENDSUB:
270                  assert(call_stack > 0);
271                  call_stack--;
272                  break;
273               case TGSI_OPCODE_BGNSUB:
274               case TGSI_OPCODE_RET:
275               default:
276                  break;
277               }
278               if (ctx->transform_instruction)
279                  ctx->transform_instruction(ctx, fullinst);
280               else
281                  ctx->emit_instruction(ctx, fullinst);
282            }
283
284            first_instruction = FALSE;
285         }
286         break;
287
288      case TGSI_TOKEN_TYPE_DECLARATION:
289         {
290            struct tgsi_full_declaration *fulldecl
291               = &parse.FullToken.FullDeclaration;
292
293            if (ctx->transform_declaration)
294               ctx->transform_declaration(ctx, fulldecl);
295            else
296               ctx->emit_declaration(ctx, fulldecl);
297         }
298         break;
299
300      case TGSI_TOKEN_TYPE_IMMEDIATE:
301         {
302            struct tgsi_full_immediate *fullimm
303               = &parse.FullToken.FullImmediate;
304
305            if (ctx->transform_immediate)
306               ctx->transform_immediate(ctx, fullimm);
307            else
308               ctx->emit_immediate(ctx, fullimm);
309         }
310         break;
311      case TGSI_TOKEN_TYPE_PROPERTY:
312         {
313            struct tgsi_full_property *fullprop
314               = &parse.FullToken.FullProperty;
315
316            if (ctx->transform_property)
317               ctx->transform_property(ctx, fullprop);
318            else
319               ctx->emit_property(ctx, fullprop);
320         }
321         break;
322
323      default:
324         assert( 0 );
325      }
326   }
327   assert(call_stack == 0);
328
329   tgsi_parse_free (&parse);
330
331   if (ctx->fail) {
332      tgsi_free_tokens(ctx->tokens_out);
333      return NULL;
334   }
335
336   return ctx->tokens_out;
337}
338
339
340#include "tgsi_text.h"
341
342extern int tgsi_transform_foo( struct tgsi_token *tokens_out,
343                               uint max_tokens_out );
344
345/* This function exists only so that tgsi_text_translate() doesn't get
346 * magic-ed out of the libtgsi.a archive by the build system.  Don't
347 * remove unless you know this has been fixed - check on mingw/scons
348 * builds as well.
349 */
350int
351tgsi_transform_foo( struct tgsi_token *tokens_out,
352                    uint max_tokens_out )
353{
354   const char *text =
355      "FRAG\n"
356      "DCL IN[0], COLOR, CONSTANT\n"
357      "DCL OUT[0], COLOR\n"
358      "  0: MOV OUT[0], IN[0]\n"
359      "  1: END";
360
361   return tgsi_text_translate( text,
362                               tokens_out,
363                               max_tokens_out );
364}
365