xref: /aosp_15_r20/external/mesa3d/src/gallium/drivers/r600/sfn/sfn_instr_fetch.cpp (revision 6104692788411f58d303aa86923a9ff6ecaded22)
1 /* -*- mesa-c++  -*-
2  * Copyright 2022 Collabora LTD
3  * Author: Gert Wollny <[email protected]>
4  * SPDX-License-Identifier: MIT
5  */
6 
7 #include "sfn_instr_fetch.h"
8 
9 #include "sfn_alu_defines.h"
10 #include "sfn_defines.h"
11 #include "sfn_valuefactory.h"
12 
13 #include <sstream>
14 
15 namespace r600 {
16 
17 using std::istringstream;
18 using std::string;
19 
FetchInstr(EVFetchInstr opcode,const RegisterVec4 & dst,const RegisterVec4::Swizzle & dest_swizzle,PRegister src,uint32_t src_offset,EVFetchType fetch_type,EVTXDataFormat data_format,EVFetchNumFormat num_format,EVFetchEndianSwap endian_swap,uint32_t resource_id,PRegister resource_offset)20 FetchInstr::FetchInstr(EVFetchInstr opcode,
21                        const RegisterVec4& dst,
22                        const RegisterVec4::Swizzle& dest_swizzle,
23                        PRegister src,
24                        uint32_t src_offset,
25                        EVFetchType fetch_type,
26                        EVTXDataFormat data_format,
27                        EVFetchNumFormat num_format,
28                        EVFetchEndianSwap endian_swap,
29                        uint32_t resource_id,
30                        PRegister resource_offset):
31     InstrWithVectorResult(dst, dest_swizzle, resource_id, resource_offset),
32     m_opcode(opcode),
33     m_src(src),
34     m_src_offset(src_offset),
35     m_fetch_type(fetch_type),
36     m_data_format(data_format),
37     m_num_format(num_format),
38     m_endian_swap(endian_swap),
39     m_mega_fetch_count(0),
40     m_array_base(0),
41     m_array_size(0),
42     m_elm_size(0)
43 {
44    switch (m_opcode) {
45    case vc_fetch:
46       m_opname = "VFETCH";
47       break;
48    case vc_semantic:
49       m_opname = "FETCH_SEMANTIC";
50       break;
51    case vc_get_buf_resinfo:
52       set_print_skip(mfc);
53       set_print_skip(fmt);
54       set_print_skip(ftype);
55       m_opname = "GET_BUF_RESINFO";
56       break;
57    case vc_read_scratch:
58       m_opname = "READ_SCRATCH";
59       break;
60    default:
61       unreachable("Unknown fetch instruction");
62    }
63 
64    if (m_src)
65       m_src->add_use(this);
66 }
67 
68 void
accept(ConstInstrVisitor & visitor) const69 FetchInstr::accept(ConstInstrVisitor& visitor) const
70 {
71    visitor.visit(*this);
72 }
73 
74 void
accept(InstrVisitor & visitor)75 FetchInstr::accept(InstrVisitor& visitor)
76 {
77    visitor.visit(this);
78 }
79 
80 bool
is_equal_to(const FetchInstr & rhs) const81 FetchInstr::is_equal_to(const FetchInstr& rhs) const
82 {
83    if (m_src) {
84       if (rhs.m_src) {
85          if (!m_src->equal_to(*rhs.m_src))
86             return false;
87       } else
88          return false;
89    } else if (rhs.m_src)
90       return false;
91 
92    if (!comp_dest(rhs.dst(), rhs.all_dest_swizzle()))
93       return false;
94 
95    if (m_tex_flags != rhs.m_tex_flags)
96       return false;
97 
98    if (resource_offset() && rhs.resource_offset()) {
99       if (!resource_offset()->equal_to(*rhs.resource_offset()))
100          return false;
101    } else if (!(!!resource_offset() == !!rhs.resource_offset()))
102       return false;
103 
104    return m_opcode == rhs.m_opcode && m_src_offset == rhs.m_src_offset &&
105           m_fetch_type == rhs.m_fetch_type && m_data_format == rhs.m_data_format &&
106           m_num_format == rhs.m_num_format && m_endian_swap == rhs.m_endian_swap &&
107           m_mega_fetch_count == rhs.m_mega_fetch_count &&
108           m_array_base == rhs.m_array_base && m_array_size == rhs.m_array_size &&
109           m_elm_size == rhs.m_elm_size && resource_id() == rhs.resource_id();
110 }
111 
112 bool
propagate_death()113 FetchInstr::propagate_death()
114 {
115    auto reg = m_src->as_register();
116    if (reg)
117       reg->del_use(this);
118    return true;
119 }
120 
121 bool
replace_source(PRegister old_src,PVirtualValue new_src)122 FetchInstr::replace_source(PRegister old_src, PVirtualValue new_src)
123 {
124    bool success = false;
125    auto new_reg = new_src->as_register();
126    if (new_reg) {
127       if (old_src->equal_to(*m_src)) {
128          m_src->del_use(this);
129          m_src = new_reg;
130          new_reg->add_use(this);
131          success = true;
132       }
133       success |= replace_resource_offset(old_src, new_reg);
134    }
135    return success;
136 }
137 
138 bool
do_ready() const139 FetchInstr::do_ready() const
140 {
141    for (auto i : required_instr()) {
142       if (!i->is_scheduled())
143          return false;
144    }
145 
146    bool result = m_src && m_src->ready(block_id(), index());
147    if (resource_offset())
148       result &= resource_offset()->ready(block_id(), index());
149    return result;
150 }
151 
152 void
do_print(std::ostream & os) const153 FetchInstr::do_print(std::ostream& os) const
154 {
155    os << m_opname << ' ';
156 
157    print_dest(os);
158 
159    os << " :";
160 
161    if (m_opcode != vc_get_buf_resinfo) {
162 
163       if (m_src && m_src->chan() < 7) {
164          os << " " << *m_src;
165          if (m_src_offset)
166             os << " + " << m_src_offset << "b";
167       }
168    }
169 
170    if (m_opcode != vc_read_scratch)
171       os << " RID:" << resource_id();
172 
173    print_resource_offset(os);
174 
175    if (!m_skip_print.test(ftype)) {
176       switch (m_fetch_type) {
177       case vertex_data:
178          os << " VERTEX";
179          break;
180       case instance_data:
181          os << " INSTANCE_DATA";
182          break;
183       case no_index_offset:
184          os << " NO_IDX_OFFSET";
185          break;
186       default:
187          unreachable("Unknown fetch instruction type");
188       }
189    }
190 
191    if (!m_skip_print.test(fmt)) {
192       os << " FMT(";
193       auto fmt = s_data_format_map.find(m_data_format);
194       if (fmt != s_data_format_map.end())
195          os << fmt->second << ",";
196       else
197          unreachable("unknown data format");
198 
199       if (m_tex_flags.test(format_comp_signed))
200          os << "S";
201       else
202          os << "U";
203 
204       switch (m_num_format) {
205       case vtx_nf_norm:
206          os << "NORM";
207          break;
208       case vtx_nf_int:
209          os << "INT";
210          break;
211       case vtx_nf_scaled:
212          os << "SCALED";
213          break;
214       default:
215          unreachable("Unknown number format");
216       }
217 
218       os << ")";
219    }
220 
221    if (m_array_base) {
222       if (m_opcode != vc_read_scratch)
223          os << " BASE:" << m_array_base;
224       else
225          os << " L[0x" << std::uppercase << std::hex << m_array_base << std::dec << "]";
226    }
227 
228    if (m_array_size)
229       os << " SIZE:" << m_array_size + 1;
230 
231    if (m_tex_flags.test(is_mega_fetch) && !m_skip_print.test(mfc))
232       os << " MFC:" << m_mega_fetch_count;
233 
234    if (m_elm_size)
235       os << " ES:" << m_elm_size;
236 
237    if (m_tex_flags.test(fetch_whole_quad))
238       os << " WQ";
239    if (m_tex_flags.test(use_const_field))
240       os << " UCF";
241    if (m_tex_flags.test(srf_mode))
242       os << " SRF";
243    if (m_tex_flags.test(buf_no_stride))
244       os << " BNS";
245    if (m_tex_flags.test(alt_const))
246       os << " AC";
247    if (m_tex_flags.test(use_tc))
248       os << " TC";
249    if (m_tex_flags.test(vpm))
250       os << " VPM";
251    if (m_tex_flags.test(uncached) && m_opcode != vc_read_scratch)
252       os << " UNCACHED";
253    if (m_tex_flags.test(indexed) && m_opcode != vc_read_scratch)
254       os << " INDEXED";
255 }
256 
257 Instr::Pointer
from_string(std::istream & is,ValueFactory & vf)258 FetchInstr::from_string(std::istream& is, ValueFactory& vf)
259 {
260    return from_string_impl(is, vc_fetch, vf);
261 }
262 
263 Instr::Pointer
from_string_impl(std::istream & is,EVFetchInstr opcode,ValueFactory & vf)264 FetchInstr::from_string_impl(std::istream& is, EVFetchInstr opcode, ValueFactory& vf)
265 {
266    std::string deststr;
267    is >> deststr;
268 
269    RegisterVec4::Swizzle dst_swz;
270    auto dest_reg = vf.dest_vec4_from_string(deststr, dst_swz, pin_group);
271 
272    char help;
273    is >> help;
274    assert(help == ':');
275 
276    string srcstr;
277    is >> srcstr;
278 
279    std::cerr << "Get source " << srcstr << "\n";
280 
281    auto src_reg = vf.src_from_string(srcstr)->as_register();
282    assert(src_reg);
283 
284    string res_id_str;
285    string next;
286    is >> next;
287 
288    int src_offset_val = 0;
289 
290    if (next == "+") {
291       is >> src_offset_val;
292       is >> help;
293       assert(help == 'b');
294       is >> res_id_str;
295    } else {
296       res_id_str = next;
297    }
298 
299    int res_id = int_from_string_with_prefix(res_id_str, "RID:");
300 
301    string fetch_type_str;
302    is >> fetch_type_str;
303 
304    EVFetchType fetch_type = vertex_data;
305    if (fetch_type_str == "VERTEX") {
306       fetch_type = vertex_data;
307    } else {
308       assert("Fetch type not yet implemented");
309    }
310 
311    string format_str;
312    is >> format_str;
313 
314    assert(!strncmp(format_str.c_str(), "FMT(", 4));
315    string data_format;
316    string num_format_str;
317 
318    istringstream fmt_stream(format_str.substr(4));
319    bool is_num_fmr = false;
320    assert(!fmt_stream.eof());
321 
322    do {
323       char c;
324       fmt_stream >> c;
325 
326       if (c == ',') {
327          is_num_fmr = true;
328          continue;
329       }
330 
331       if (!is_num_fmr)
332          data_format.append(1, c);
333       else
334          num_format_str.append(1, c);
335    } while (!fmt_stream.eof());
336 
337    EVTXDataFormat fmt = fmt_invalid;
338 
339    for (auto& [f, name] : s_data_format_map) {
340       if (data_format == name) {
341          fmt = f;
342          break;
343       }
344    }
345 
346    assert(fmt != fmt_invalid);
347 
348    bool fmt_signed = num_format_str[0] == 'S';
349    assert(fmt_signed || num_format_str[0] == 'U');
350 
351    size_t num_format_end = num_format_str.find(')');
352    num_format_str = num_format_str.substr(1, num_format_end - 1);
353 
354    EVFetchNumFormat num_fmt;
355    if (num_format_str == "NORM")
356       num_fmt = vtx_nf_norm;
357    else if (num_format_str == "INT")
358       num_fmt = vtx_nf_int;
359    else if (num_format_str == "SCALED")
360       num_fmt = vtx_nf_scaled;
361    else {
362       std::cerr << "Number format: '" << num_format_str << "' : ";
363       unreachable("Unknown number format");
364    }
365 
366    auto fetch = new FetchInstr(opcode,
367                                dest_reg,
368                                dst_swz,
369                                src_reg,
370                                src_offset_val,
371                                fetch_type,
372                                fmt,
373                                num_fmt,
374                                vtx_es_none,
375                                res_id,
376                                nullptr);
377    if (fmt_signed)
378       fetch->set_fetch_flag(format_comp_signed);
379 
380    while (!is.eof() && is.good()) {
381       std::string next_token;
382       is >> next_token;
383 
384       if (next_token.empty())
385          break;
386 
387       if (next_token.find(':') != string::npos) {
388          fetch->set_param_from_string(next_token);
389       } else {
390          fetch->set_flag_from_string(next_token);
391       }
392    }
393 
394    return fetch;
395 }
396 
397 void
set_param_from_string(const std::string & token)398 FetchInstr::set_param_from_string(const std::string& token)
399 {
400    if (token.substr(0, 4) == "MFC:")
401       set_mfc(int_from_string_with_prefix(token, "MFC:"));
402    else if (token.substr(0, 5) == "ARRB:")
403       set_array_base(int_from_string_with_prefix(token, "ARRB:"));
404    else if (token.substr(0, 5) == "ARRS:")
405       set_array_size(int_from_string_with_prefix(token, "ARRS:"));
406    else if (token.substr(0, 3) == "ES:")
407       set_element_size(int_from_string_with_prefix(token, "ES:"));
408    else {
409       std::cerr << "Token '" << token << "': ";
410       unreachable("Unknown token in fetch param list");
411    }
412 }
413 
414 void
set_flag_from_string(const std::string & token)415 FetchInstr::set_flag_from_string(const std::string& token)
416 {
417    auto flag = s_flag_map.find(token.c_str());
418    if (flag != s_flag_map.end())
419       set_fetch_flag(flag->second);
420    else {
421       std::cerr << "Token: " << token << " : ";
422       unreachable("Unknown token in fetch flag list");
423    }
424 }
425 
426 const std::map<const char *, FetchInstr::EFlags> FetchInstr::s_flag_map = {
427    {"WQ",       fetch_whole_quad},
428    {"UCF",      use_const_field },
429    {"SRF",      srf_mode        },
430    {"BNS",      buf_no_stride   },
431    {"AC",       alt_const       },
432    {"TC",       use_tc          },
433    {"VPM",      vpm             },
434    {"UNCACHED", uncached        },
435    {"INDEXED",  indexed         }
436 };
437 
438 const std::map<EVTXDataFormat, const char *> FetchInstr::s_data_format_map = {
439    {fmt_invalid,           "INVALID"          },
440    {fmt_8,                 "8"                },
441    {fmt_4_4,               "4_4"              },
442    {fmt_3_3_2,             "3_3_2"            },
443    {fmt_reserved_4,        "RESERVED_4"       },
444    {fmt_16,                "16"               },
445    {fmt_16_float,          "16F"              },
446    {fmt_8_8,               "8_8"              },
447    {fmt_5_6_5,             "5_6_5"            },
448    {fmt_6_5_5,             "6_5_5"            },
449    {fmt_1_5_5_5,           "1_5_5_5"          },
450    {fmt_4_4_4_4,           "4_4_4_4"          },
451    {fmt_5_5_5_1,           "5_5_5_1"          },
452    {fmt_32,                "32"               },
453    {fmt_32_float,          "32F"              },
454    {fmt_16_16,             "16_16"            },
455    {fmt_16_16_float,       "16_16F"           },
456    {fmt_8_24,              "8_24"             },
457    {fmt_8_24_float,        "8_24F"            },
458    {fmt_24_8,              "24_8"             },
459    {fmt_24_8_float,        "24_8F"            },
460    {fmt_10_11_11,          "10_11_11"         },
461    {fmt_10_11_11_float,    "10_11_11F"        },
462    {fmt_11_11_10,          "11_11_10"         },
463    {fmt_10_11_11_float,    "11_11_10F"        },
464    {fmt_2_10_10_10,        "2_10_10_10"       },
465    {fmt_8_8_8_8,           "8_8_8_8"          },
466    {fmt_10_10_10_2,        "10_10_10_2"       },
467    {fmt_x24_8_32_float,    "X24_8_32F"        },
468    {fmt_32_32,             "32_32"            },
469    {fmt_32_32_float,       "32_32F"           },
470    {fmt_16_16_16_16,       "16_16_16_16"      },
471    {fmt_16_16_16_16_float, "16_16_16_16F"     },
472    {fmt_reserved_33,       "RESERVED_33"      },
473    {fmt_32_32_32_32,       "32_32_32_32"      },
474    {fmt_32_32_32_32_float, "32_32_32_32F"     },
475    {fmt_reserved_36,       "RESERVED_36"      },
476    {fmt_1,                 "1"                },
477    {fmt_1_reversed,        "1_REVERSED"       },
478    {fmt_gb_gr,             "GB_GR"            },
479    {fmt_bg_rg,             "BG_RG"            },
480    {fmt_32_as_8,           "32_AS_8"          },
481    {fmt_32_as_8_8,         "32_AS_8_8"        },
482    {fmt_5_9_9_9_sharedexp, "5_9_9_9_SHAREDEXP"},
483    {fmt_8_8_8,             "8_8_8"            },
484    {fmt_16_16_16,          "16_16_16"         },
485    {fmt_16_16_16_float,    "16_16_16F"        },
486    {fmt_32_32_32,          "32_32_32"         },
487    {fmt_32_32_32_float,    "32_32_32F"        },
488    {fmt_bc1,               "BC1"              },
489    {fmt_bc2,               "BC2"              },
490    {fmt_bc3,               "BC3"              },
491    {fmt_bc4,               "BC4"              },
492    {fmt_bc5,               "BC5"              },
493    {fmt_apc0,              "APC0"             },
494    {fmt_apc1,              "APC1"             },
495    {fmt_apc2,              "APC2"             },
496    {fmt_apc3,              "APC3"             },
497    {fmt_apc4,              "APC4"             },
498    {fmt_apc5,              "APC5"             },
499    {fmt_apc6,              "APC6"             },
500    {fmt_apc7,              "APC7"             },
501    {fmt_ctx1,              "CTX1"             },
502    {fmt_reserved_63,       "RESERVED_63"      }
503 };
504 
QueryBufferSizeInstr(const RegisterVec4 & dst,const RegisterVec4::Swizzle & dst_swz,uint32_t resid)505 QueryBufferSizeInstr::QueryBufferSizeInstr(const RegisterVec4& dst,
506                                            const RegisterVec4::Swizzle& dst_swz,
507                                            uint32_t resid):
508     FetchInstr(vc_get_buf_resinfo,
509                dst,
510                dst_swz,
511                new Register(0, 7, pin_fully),
512                0,
513                no_index_offset,
514                fmt_32_32_32_32,
515                vtx_nf_norm,
516                vtx_es_none,
517                resid,
518                nullptr)
519 {
520    set_fetch_flag(format_comp_signed);
521    set_print_skip(mfc);
522    set_print_skip(fmt);
523    set_print_skip(ftype);
524 }
525 
526 Instr::Pointer
from_string(std::istream & is,ValueFactory & vf)527 QueryBufferSizeInstr::from_string(std::istream& is, ValueFactory& vf)
528 {
529    std::string deststr, res_id_str;
530    is >> deststr;
531 
532    char help;
533    is >> help;
534    assert(help == ':');
535 
536    is >> res_id_str;
537 
538    RegisterVec4::Swizzle dst_swz;
539    auto dst = vf.dest_vec4_from_string(deststr, dst_swz, pin_group);
540    int res_id = int_from_string_with_prefix(res_id_str, "RID:");
541 
542    return new QueryBufferSizeInstr(dst, dst_swz, res_id);
543 }
544 
LoadFromBuffer(const RegisterVec4 & dst,const RegisterVec4::Swizzle & dst_swizzle,PRegister addr,uint32_t addr_offset,uint32_t resid,PRegister res_offset,EVTXDataFormat data_format)545 LoadFromBuffer::LoadFromBuffer(const RegisterVec4& dst,
546                                const RegisterVec4::Swizzle& dst_swizzle,
547                                PRegister addr,
548                                uint32_t addr_offset,
549                                uint32_t resid,
550                                PRegister res_offset,
551                                EVTXDataFormat data_format):
552     FetchInstr(vc_fetch,
553                dst,
554                dst_swizzle,
555                addr,
556                addr_offset,
557                no_index_offset,
558                data_format,
559                vtx_nf_scaled,
560                vtx_es_none,
561                resid,
562                res_offset)
563 {
564    set_fetch_flag(format_comp_signed);
565    set_mfc(16);
566    override_opname("LOAD_BUF");
567    set_print_skip(mfc);
568    set_print_skip(fmt);
569    set_print_skip(ftype);
570 }
571 
572 Instr::Pointer
from_string(std::istream & is,ValueFactory & vf)573 LoadFromBuffer::from_string(std::istream& is, ValueFactory& vf)
574 {
575    std::string deststr;
576    is >> deststr;
577 
578    RegisterVec4::Swizzle dst_swz;
579    auto dst = vf.dest_vec4_from_string(deststr, dst_swz, pin_group);
580 
581    char help;
582    is >> help;
583    assert(help == ':');
584 
585    string addrstr;
586    is >> addrstr;
587    auto addr_reg = vf.src_from_string(addrstr)->as_register();
588 
589    string res_id_str;
590    string next;
591    is >> next;
592 
593    int addr_offset_val = 0;
594 
595    if (next == "+") {
596       is >> addr_offset_val;
597       is >> help;
598       assert(help == 'b');
599       is >> res_id_str;
600    } else {
601       res_id_str = next;
602    }
603 
604    int res_id = int_from_string_with_prefix(res_id_str, "RID:");
605 
606    next.clear();
607    is >> next;
608    PRegister res_offset = nullptr;
609    if (next == "+") {
610       string res_offset_str;
611       is >> res_offset_str;
612       res_offset = vf.src_from_string(res_offset_str)->as_register();
613    }
614 
615    auto fetch = new LoadFromBuffer(
616       dst, dst_swz, addr_reg, addr_offset_val, res_id, res_offset, fmt_32_32_32_32_float);
617    is >> next;
618    if (next == "SRF")
619       fetch->set_fetch_flag(srf_mode);
620 
621    return fetch;
622 }
623 
624 class AddrResolver : public RegisterVisitor {
625 public:
AddrResolver(LoadFromScratch * lfs)626    AddrResolver(LoadFromScratch *lfs):
627        m_lfs(lfs)
628    {
629    }
630 
visit(Register & value)631    void visit(Register& value)
632    {
633       m_lfs->set_fetch_flag(FetchInstr::indexed);
634       m_lfs->set_src(&value);
635       value.add_use(m_lfs);
636    }
visit(LocalArray & value)637    void visit(LocalArray& value)
638    {
639       unreachable("An array can't be a direct source for scratch reads");
640       (void)value;
641    }
visit(LocalArrayValue & value)642    void visit(LocalArrayValue& value)
643    {
644       unreachable("An array value can't be a direct source for scratch reads");
645       // TODO: an array element with constant offset could be used here
646       (void)value;
647    }
visit(UniformValue & value)648    void visit(UniformValue& value)
649    {
650       unreachable("A uniform can't be a direct source for scratch reads");
651       (void)value;
652    }
visit(LiteralConstant & value)653    void visit(LiteralConstant& value)
654    {
655       m_lfs->set_array_base(value.value());
656       m_lfs->set_src(new Register(0, 7, pin_none));
657    }
visit(InlineConstant & value)658    void visit(InlineConstant& value)
659    {
660       if (value.sel() == ALU_SRC_1_INT)
661          m_lfs->set_array_base(1);
662       else if (value.sel() != ALU_SRC_0)
663          unreachable("Scratch array base is an impossible inline constant");
664 
665       m_lfs->set_src(new Register(0, 7, pin_none));
666    }
667 
668    LoadFromScratch *m_lfs;
669 };
670 
LoadFromScratch(const RegisterVec4 & dst,const RegisterVec4::Swizzle & dst_swz,PVirtualValue addr,uint32_t scratch_size)671 LoadFromScratch::LoadFromScratch(const RegisterVec4& dst,
672                                  const RegisterVec4::Swizzle& dst_swz,
673                                  PVirtualValue addr,
674                                  uint32_t scratch_size):
675     FetchInstr(vc_read_scratch,
676                dst,
677                dst_swz,
678                nullptr,
679                0,
680                no_index_offset,
681                fmt_32_32_32_32,
682                vtx_nf_int,
683                vtx_es_none,
684                0,
685                nullptr)
686 {
687    set_fetch_flag(uncached);
688    set_fetch_flag(wait_ack);
689 
690    assert(scratch_size >= 1);
691    set_array_size(scratch_size - 1);
692    set_array_base(0);
693    AddrResolver ar(this);
694    addr->accept(ar);
695 
696    set_print_skip(mfc);
697    set_print_skip(fmt);
698    set_print_skip(ftype);
699    set_element_size(3);
700 }
701 
702 Instr::Pointer
from_string(std::istream & is,ValueFactory & vf)703 LoadFromScratch::from_string(std::istream& is, ValueFactory& vf)
704 {
705    std::string deststr;
706    is >> deststr;
707 
708    RegisterVec4::Swizzle dst_swz;
709    auto dest = vf.dest_vec4_from_string(deststr, dst_swz, pin_group);
710 
711    char help;
712    is >> help;
713    assert(help == ':');
714 
715    string addrstr;
716    is >> addrstr;
717    auto addr_reg = vf.src_from_string(addrstr);
718 
719    string offsetstr;
720    is >> offsetstr;
721    int size = int_from_string_with_prefix(offsetstr, "SIZE:");
722    assert(size >= 1);
723 
724    return new LoadFromScratch(dest, dst_swz, addr_reg, size);
725 }
726 
727 } // namespace r600
728