1use std::collections::{BTreeMap, BTreeSet};
2
3use crate::ctab::{ConstantInfo, ConstantTable, RegisterSet, ValueType};
4use crate::disasm::{
5 DeclUsage, Instruction, Opcode, RegisterKey, RegisterType, ResultModifier, SamplerTextureType,
6 ShaderKind, SourceModifier, mask_len,
7};
8use crate::disasm::{ShaderModel, parse_shader};
9
10#[derive(Debug, Clone)]
11struct DeclInfo {
12 reg: RegisterKey,
13 semantic: String,
14}
15
16#[derive(Debug, Clone)]
17struct DefFloat {
18 values: [f32; 4],
19}
20
21#[derive(Debug, Clone)]
22struct DefInt {
23 values: [i32; 4],
24}
25
26#[derive(Debug, Clone)]
27struct DefBool {
28 value: bool,
29}
30
31#[derive(Debug, Clone)]
32struct Context<'a> {
33 shader: &'a ShaderModel,
34 ctab: Option<&'a ConstantTable>,
35 decls: BTreeMap<RegisterKey, DeclInfo>,
36 sampler_decls: BTreeMap<u16, SamplerTextureType>,
37 def_float: BTreeMap<u16, DefFloat>,
38 def_int: BTreeMap<u16, DefInt>,
39 def_bool: BTreeMap<u16, DefBool>,
40 used_inputs: BTreeSet<RegisterKey>,
41 used_outputs: BTreeSet<RegisterKey>,
42 used_temps: BTreeSet<RegisterKey>,
43 used_samplers: BTreeSet<u16>,
44 used_consts: BTreeSet<u16>,
45 used_int_consts: BTreeSet<u16>,
46 used_bool_consts: BTreeSet<u16>,
47 uses_lit: bool,
48 uses_dst: bool,
49}
50
51pub fn decompile_wgsl(data: &[u8], ctab: Option<&ConstantTable>) -> String {
52 match parse_shader(data) {
53 Ok(shader) => {
54 let ctx = analyze(&shader, ctab);
55 emit_wgsl(&ctx)
56 }
57 Err(_) => String::new(),
58 }
59}
60
61fn emit_wgsl(ctx: &Context<'_>) -> String {
62 let mut out = String::new();
63 emit_register_bindings(&mut out, ctx);
64 emit_texture_bindings(&mut out, ctx);
65 emit_def_constants(&mut out, ctx);
66 emit_helpers(&mut out, ctx);
67 emit_structs(&mut out, ctx);
68 emit_main(&mut out, ctx);
69 out
70}
71
72fn emit_register_bindings(out: &mut String, ctx: &Context<'_>) {
73 if !ctx.used_consts.is_empty() {
74 out.push_str("struct FloatRegs {\n");
75 out.push_str(" c: array<vec4<f32>, 256>,\n");
76 out.push_str("};\n");
77 out.push_str("@group(0) @binding(0) var<uniform> float_regs: FloatRegs;\n\n");
78 }
79 if !ctx.used_int_consts.is_empty() {
80 out.push_str("struct IntRegs {\n");
81 out.push_str(" i: array<vec4<i32>, 16>,\n");
82 out.push_str("};\n");
83 out.push_str("@group(0) @binding(1) var<uniform> int_regs: IntRegs;\n\n");
84 }
85 if !ctx.used_bool_consts.is_empty() {
86 out.push_str("struct BoolRegs {\n");
87 out.push_str(" b: array<u32, 16>,\n");
88 out.push_str("};\n");
89 out.push_str("@group(0) @binding(2) var<uniform> bool_regs: BoolRegs;\n\n");
90 }
91}
92
93fn emit_texture_bindings(out: &mut String, ctx: &Context<'_>) {
94 for sampler in &ctx.used_samplers {
95 let tex_binding = (*sampler as u32) * 2;
96 let samp_binding = tex_binding + 1;
97 let tex_ty = wgsl_texture_type(sampler_texture_type(ctx, *sampler));
98 out.push_str(&format!(
99 "@group(1) @binding({}) var tex_s{}: {};\n",
100 tex_binding, sampler, tex_ty
101 ));
102 out.push_str(&format!(
103 "@group(1) @binding({}) var samp_s{}: sampler;\n",
104 samp_binding, sampler
105 ));
106 }
107 if !ctx.used_samplers.is_empty() {
108 out.push('\n');
109 }
110}
111
112fn emit_def_constants(out: &mut String, ctx: &Context<'_>) {
113 let mut any = false;
114 for (idx, def) in &ctx.def_float {
115 out.push_str(&format!(
116 "const c{}: vec4<f32> = vec4<f32>({}, {}, {}, {});\n",
117 idx,
118 fmt_f32(def.values[0]),
119 fmt_f32(def.values[1]),
120 fmt_f32(def.values[2]),
121 fmt_f32(def.values[3])
122 ));
123 any = true;
124 }
125 for (idx, def) in &ctx.def_int {
126 out.push_str(&format!(
127 "const i{}: vec4<i32> = vec4<i32>({}, {}, {}, {});\n",
128 idx, def.values[0], def.values[1], def.values[2], def.values[3]
129 ));
130 any = true;
131 }
132 for (idx, def) in &ctx.def_bool {
133 out.push_str(&format!(
134 "const b{}: bool = {};\n",
135 idx,
136 if def.value { "true" } else { "false" }
137 ));
138 any = true;
139 }
140 if any {
141 out.push('\n');
142 }
143}
144
145fn emit_helpers(out: &mut String, ctx: &Context<'_>) {
146 if ctx.uses_lit {
147 out.push_str("fn sm2_lit(v: vec4<f32>) -> vec4<f32> {\n");
148 out.push_str(" let y = max(v.x, 0.0);\n");
149 out.push_str(" let z = select(0.0, pow(max(v.y, 0.0), v.w), v.x > 0.0 && v.y > 0.0);\n");
150 out.push_str(" return vec4<f32>(1.0, y, z, 1.0);\n");
151 out.push_str("}\n\n");
152 }
153 if ctx.uses_dst {
154 out.push_str("fn sm2_dst(a: vec4<f32>, b: vec4<f32>) -> vec4<f32> {\n");
155 out.push_str(" return vec4<f32>(1.0, a.y * b.y, a.z, b.w);\n");
156 out.push_str("}\n\n");
157 }
158}
159
160fn emit_structs(out: &mut String, ctx: &Context<'_>) {
161 let input_name = input_struct_name(ctx.shader.kind);
162 let output_name = output_struct_name(ctx.shader.kind);
163
164 out.push_str(&format!("struct {} {{\n", input_name));
165 for decl in ctx.decls.values() {
166 let field = input_field_name(decl.reg);
167 let ty = wgsl_input_field_type(ctx, decl.reg);
168 let attr = input_attr(ctx.shader.kind, &decl.semantic);
169 out.push_str(&format!(" {} {}: {},\n", attr, field, ty));
170 }
171 out.push_str("};\n\n");
172
173 out.push_str(&format!("struct {} {{\n", output_name));
174 for reg in &ctx.used_outputs {
175 let field = output_field_name(*reg);
176 let sem = output_semantic(ctx, *reg);
177 let ty = wgsl_output_field_type(*reg);
178 let attr = output_attr(ctx.shader.kind, &sem, *reg);
179 out.push_str(&format!(" {} {}: {},\n", attr, field, ty));
180 }
181 out.push_str("};\n\n");
182}
183
184fn emit_main(out: &mut String, ctx: &Context<'_>) {
185 let stage = match ctx.shader.kind {
186 ShaderKind::Vertex => "vertex",
187 ShaderKind::Pixel => "fragment",
188 };
189 let input_name = input_struct_name(ctx.shader.kind);
190 let output_name = output_struct_name(ctx.shader.kind);
191 out.push_str(&format!("@{}\n", stage));
192 out.push_str(&format!(
193 "fn main(input: {}) -> {} {{\n",
194 input_name, output_name
195 ));
196 out.push_str(&format!(" var output: {};\n", output_name));
197
198 for reg in &ctx.used_outputs {
199 out.push_str(&format!(
200 " output.{} = {};\n",
201 output_field_name(*reg),
202 zero_value(wgsl_output_field_type(*reg))
203 ));
204 }
205 for reg in &ctx.used_temps {
206 out.push_str(&format!(
207 " var {}: {} = {};\n",
208 temp_name(*reg, ctx.shader.kind),
209 temp_type(*reg),
210 zero_value(temp_type(*reg))
211 ));
212 }
213 if !ctx.used_outputs.is_empty() || !ctx.used_temps.is_empty() {
214 out.push('\n');
215 }
216
217 let mut indent = 1usize;
218 for inst in &ctx.shader.instructions {
219 emit_instruction(out, ctx, inst, &mut indent);
220 }
221
222 out.push_str(" return output;\n");
223 out.push_str("}\n");
224}
225
226fn emit_instruction(out: &mut String, ctx: &Context<'_>, inst: &Instruction, indent: &mut usize) {
227 match inst.opcode {
228 Opcode::Comment
229 | Opcode::Dcl
230 | Opcode::Def
231 | Opcode::DefI
232 | Opcode::DefB
233 | Opcode::Nop
234 | Opcode::End => {}
235 Opcode::TexKill => {
236 if let Some(reg) = inst.dest_register() {
237 let expr = format!(
238 "{}{}",
239 register_base(ctx, reg),
240 wgsl_mask_suffix(inst.dest_write_mask())
241 );
242 let n = mask_len(inst.dest_write_mask());
243 if n == 1 {
244 line(out, *indent, &format!("if ({} < 0.0) {{ discard; }}", expr));
245 } else {
246 line(
247 out,
248 *indent,
249 &format!("if (any({} < {})) {{ discard; }}", expr, zero_vector(n)),
250 );
251 }
252 }
253 }
254 Opcode::If => {
255 let cond = source_expr(ctx, inst, 0, 1);
256 line(out, *indent, &format!("if ({}) {{", scalar_bool_expr(cond)));
257 *indent += 1;
258 }
259 Opcode::IfC => {
260 let a = source_expr(ctx, inst, 0, 4);
261 let b = source_expr(ctx, inst, 1, 4);
262 line(
263 out,
264 *indent,
265 &format!(
266 "if ({}) {{",
267 compare_all_expr(a, cmp_op(inst.comparison()), b, 4)
268 ),
269 );
270 *indent += 1;
271 }
272 Opcode::Else => {
273 if *indent > 0 {
274 *indent -= 1;
275 }
276 line(out, *indent, "} else {");
277 *indent += 1;
278 }
279 Opcode::EndIf => {
280 if *indent > 0 {
281 *indent -= 1;
282 }
283 line(out, *indent, "}");
284 }
285 Opcode::Break => line(out, *indent, "break;"),
286 Opcode::BreakC => {
287 let a = source_expr(ctx, inst, 0, 4);
288 let b = source_expr(ctx, inst, 1, 4);
289 line(
290 out,
291 *indent,
292 &format!(
293 "if ({}) {{ break; }}",
294 compare_all_expr(a, cmp_op(inst.comparison()), b, 4)
295 ),
296 );
297 }
298 Opcode::Rep => {
299 let n = source_expr(ctx, inst, 0, 1);
300 line(
301 out,
302 *indent,
303 &format!(
304 "for (var _rep{}: i32 = 0; _rep{} < i32({}); _rep{} = _rep{} + 1) {{",
305 inst.offset, inst.offset, n, inst.offset, inst.offset
306 ),
307 );
308 *indent += 1;
309 }
310 Opcode::Loop => {
311 line(
312 out,
313 *indent,
314 &format!(
315 "for (var _loop{}: i32 = 0; ; _loop{} = _loop{} + 1) {{",
316 inst.offset, inst.offset, inst.offset
317 ),
318 );
319 *indent += 1;
320 }
321 Opcode::EndRep | Opcode::EndLoop => {
322 if *indent > 0 {
323 *indent -= 1;
324 }
325 line(out, *indent, "}");
326 }
327 Opcode::Ret => line(out, *indent, "return output;"),
328 _ if inst.opcode.has_destination() => {
329 if let Some((dst, rhs)) = assignment_expr(ctx, inst) {
330 line(out, *indent, &format!("{} = {};", dst, rhs));
331 }
332 }
333 _ => {}
334 }
335}
336
337fn assignment_expr(ctx: &Context<'_>, inst: &Instruction) -> Option<(String, String)> {
338 let reg = inst.dest_register()?;
339 let mask = inst.dest_write_mask();
340 let dst_count = mask_len(mask);
341 let dst = format!("{}{}", register_base(ctx, reg), wgsl_mask_suffix(mask));
342
343 let (raw_rhs, raw_width) = match inst.opcode {
344 Opcode::Mov | Opcode::MovA => (source_expr(ctx, inst, 1, dst_count), dst_count),
345 Opcode::Add => (bin(ctx, inst, dst_count, "+"), dst_count),
346 Opcode::Sub => (bin(ctx, inst, dst_count, "-"), dst_count),
347 Opcode::Mul => (bin(ctx, inst, dst_count, "*"), dst_count),
348 Opcode::Mad => (
349 format!(
350 "({} * {} + {})",
351 source_expr(ctx, inst, 1, dst_count),
352 source_expr(ctx, inst, 2, dst_count),
353 source_expr(ctx, inst, 3, dst_count)
354 ),
355 dst_count,
356 ),
357 Opcode::Rcp => (format!("(1.0 / {})", source_expr(ctx, inst, 1, 1)), 1),
358 Opcode::Rsq => (format!("inverseSqrt({})", source_expr(ctx, inst, 1, 1)), 1),
359 Opcode::Dp3 => (
360 format!(
361 "dot({}, {})",
362 source_expr(ctx, inst, 1, 3),
363 source_expr(ctx, inst, 2, 3)
364 ),
365 1,
366 ),
367 Opcode::Dp4 => (
368 format!(
369 "dot({}, {})",
370 source_expr(ctx, inst, 1, 4),
371 source_expr(ctx, inst, 2, 4)
372 ),
373 1,
374 ),
375 Opcode::Min => (
376 format!(
377 "min({}, {})",
378 source_expr(ctx, inst, 1, dst_count),
379 source_expr(ctx, inst, 2, dst_count)
380 ),
381 dst_count,
382 ),
383 Opcode::Max => (
384 format!(
385 "max({}, {})",
386 source_expr(ctx, inst, 1, dst_count),
387 source_expr(ctx, inst, 2, dst_count)
388 ),
389 dst_count,
390 ),
391 Opcode::Slt => (select_float(ctx, inst, dst_count, "<"), dst_count),
392 Opcode::Sge => (select_float(ctx, inst, dst_count, ">="), dst_count),
393 Opcode::Exp | Opcode::ExpP => (format!("exp2({})", source_expr(ctx, inst, 1, 1)), 1),
394 Opcode::Log | Opcode::LogP => (format!("log2({})", source_expr(ctx, inst, 1, 1)), 1),
395 Opcode::Lit => (format!("sm2_lit({})", source_expr(ctx, inst, 1, 4)), 4),
396 Opcode::Dst => (
397 format!(
398 "sm2_dst({}, {})",
399 source_expr(ctx, inst, 1, 4),
400 source_expr(ctx, inst, 2, 4)
401 ),
402 4,
403 ),
404 Opcode::Lrp => (
405 format!(
406 "mix({}, {}, {})",
407 source_expr(ctx, inst, 3, dst_count),
408 source_expr(ctx, inst, 2, dst_count),
409 source_expr(ctx, inst, 1, dst_count)
410 ),
411 dst_count,
412 ),
413 Opcode::Frc => (
414 format!("fract({})", source_expr(ctx, inst, 1, dst_count)),
415 dst_count,
416 ),
417 Opcode::Pow => (
418 format!(
419 "pow({}, {})",
420 source_expr(ctx, inst, 1, 1),
421 source_expr(ctx, inst, 2, 1)
422 ),
423 1,
424 ),
425 Opcode::Crs => (
426 format!(
427 "cross({}, {})",
428 source_expr(ctx, inst, 1, 3),
429 source_expr(ctx, inst, 2, 3)
430 ),
431 3,
432 ),
433 Opcode::Sgn => (
434 format!("sign({})", source_expr(ctx, inst, 1, dst_count)),
435 dst_count,
436 ),
437 Opcode::Abs => (
438 format!("abs({})", source_expr(ctx, inst, 1, dst_count)),
439 dst_count,
440 ),
441 Opcode::Nrm => (format!("normalize({})", source_expr(ctx, inst, 1, 3)), 3),
442 Opcode::SinCos => {
443 let width = if dst_count <= 1 { 1 } else { 2 };
444 (sincos_expr(ctx, inst, dst_count), width)
445 }
446 Opcode::Cmp => (select_expr(ctx, inst, dst_count, ">="), dst_count),
447 Opcode::Cnd => (select_expr(ctx, inst, dst_count, ">"), dst_count),
448 Opcode::Dp2Add => (
449 format!(
450 "(dot({}, {}) + {})",
451 source_expr(ctx, inst, 1, 2),
452 source_expr(ctx, inst, 2, 2),
453 source_expr(ctx, inst, 3, 1)
454 ),
455 1,
456 ),
457 Opcode::M4x4 => (matrix_mul_expr(ctx, inst, 4, 4), 4),
458 Opcode::M4x3 => (matrix_mul_expr(ctx, inst, 4, 3), 3),
459 Opcode::M3x4 => (matrix_mul_expr(ctx, inst, 3, 4), 4),
460 Opcode::M3x3 => (matrix_mul_expr(ctx, inst, 3, 3), 3),
461 Opcode::M3x2 => (matrix_mul_expr(ctx, inst, 3, 2), 2),
462 Opcode::Tex | Opcode::TexLdl | Opcode::TexLdd => (texture_expr(ctx, inst), 4),
463 Opcode::TexCoord => (source_expr(ctx, inst, 1, dst_count), dst_count),
464 Opcode::Dsx => (
465 format!("dpdx({})", source_expr(ctx, inst, 1, dst_count)),
466 dst_count,
467 ),
468 Opcode::Dsy => (
469 format!("dpdy({})", source_expr(ctx, inst, 1, dst_count)),
470 dst_count,
471 ),
472 _ => return None,
473 };
474
475 let mut rhs = coerce_expr_width(raw_rhs, raw_width, dst_count);
476 rhs = apply_result_modifier(rhs, inst.dest_modifier(), dst_count);
477 Some((dst, rhs))
478}
479fn bin(ctx: &Context<'_>, inst: &Instruction, n: usize, op: &str) -> String {
480 format!(
481 "({} {} {})",
482 source_expr(ctx, inst, 1, n),
483 op,
484 source_expr(ctx, inst, 2, n)
485 )
486}
487
488fn select_float(ctx: &Context<'_>, inst: &Instruction, n: usize, op: &str) -> String {
489 let a = source_expr(ctx, inst, 1, n);
490 let b = source_expr(ctx, inst, 2, n);
491 let zero = zero_vector(n);
492 let one = one_vector(n);
493 format!("select({}, {}, {} {} {})", zero, one, a, op, b)
494}
495
496fn select_expr(ctx: &Context<'_>, inst: &Instruction, n: usize, op: &str) -> String {
497 let a = source_expr(ctx, inst, 1, n);
498 let yes = source_expr(ctx, inst, 2, n);
499 let no = source_expr(ctx, inst, 3, n);
500 let pivot = if op == ">" {
501 half_vector(n)
502 } else {
503 zero_vector(n)
504 };
505 format!("select({}, {}, {} {} {})", no, yes, a, op, pivot)
506}
507
508fn sincos_expr(ctx: &Context<'_>, inst: &Instruction, n: usize) -> String {
509 if n <= 1 {
510 format!("cos({})", source_expr(ctx, inst, 1, 1))
511 } else {
512 format!(
513 "vec2<f32>(cos({}), sin({}))",
514 source_expr(ctx, inst, 1, 1),
515 source_expr(ctx, inst, 1, 1)
516 )
517 }
518}
519
520fn matrix_mul_expr(
521 ctx: &Context<'_>,
522 inst: &Instruction,
523 vec_len: usize,
524 out_len: usize,
525) -> String {
526 let v = source_expr(ctx, inst, 1, vec_len);
527 let mut rows = Vec::new();
528 if let Some(reg) = inst.source_register(2) {
529 if reg.ty == RegisterType::Const {
530 for i in 0..out_len {
531 rows.push(format!(
532 "dot({}, {})",
533 v,
534 const_row_expr(ctx, reg.number + i as u16)
535 ));
536 }
537 } else {
538 for _ in 0..out_len {
539 rows.push(format!(
540 "dot({}, {})",
541 v,
542 source_expr(ctx, inst, 2, vec_len)
543 ));
544 }
545 }
546 }
547 vector_constructor(&rows)
548}
549
550fn texture_expr(ctx: &Context<'_>, inst: &Instruction) -> String {
551 let sampler = inst.source_register(2).map(|r| r.number).unwrap_or(0);
552 let sampler_ty = sampler_texture_type(ctx, sampler);
553 let dim = sampler_ty.hlsl_dim();
554 let tex = format!("tex_s{}", sampler);
555 let samp = format!("samp_s{}", sampler);
556 let controls = inst.texld_controls();
557 match (inst.opcode, controls) {
558 (Opcode::TexLdl, _) => {
559 let coord = source_expr(ctx, inst, 1, dim);
560 let lod = source_component_expr(ctx, inst, 1, 3);
561 format!("textureSampleLevel({}, {}, {}, {})", tex, samp, coord, lod)
562 }
563 (Opcode::TexLdd, _) => {
564 let coord = source_expr(ctx, inst, 1, dim);
565 let ddx = source_expr(ctx, inst, 3, dim);
566 let ddy = source_expr(ctx, inst, 4, dim);
567 format!(
568 "textureSampleGrad({}, {}, {}, {}, {})",
569 tex, samp, coord, ddx, ddy
570 )
571 }
572 (Opcode::Tex, 1) => {
573 let coord = source_expr(ctx, inst, 1, dim);
574 let w = source_component_expr(ctx, inst, 1, 3);
575 format!("textureSample({}, {}, ({} / {}))", tex, samp, coord, w)
576 }
577 (Opcode::Tex, 2) => {
578 let coord = source_expr(ctx, inst, 1, dim);
579 let bias = source_component_expr(ctx, inst, 1, 3);
580 format!("textureSampleBias({}, {}, {}, {})", tex, samp, coord, bias)
581 }
582 _ => {
583 let coord = source_expr(ctx, inst, 1, dim);
584 format!("textureSample({}, {}, {})", tex, samp, coord)
585 }
586 }
587}
588fn source_expr(ctx: &Context<'_>, inst: &Instruction, param_index: usize, count: usize) -> String {
589 let Some(reg) = inst.source_register(param_index) else {
590 return zero_vector(count);
591 };
592 let count = count.clamp(1, 4);
593 let mut expr = register_base(ctx, reg);
594 if !register_is_scalar_source(ctx, reg) {
595 let swz = inst.source_swizzle(param_index);
596 expr.push_str(&wgsl_source_swizzle_suffix(swz, count));
597 }
598 apply_source_modifier(expr, inst.source_modifier(param_index), count)
599}
600
601fn source_component_expr(
602 ctx: &Context<'_>,
603 inst: &Instruction,
604 param_index: usize,
605 component: usize,
606) -> String {
607 let Some(reg) = inst.source_register(param_index) else {
608 return "0.0".to_string();
609 };
610 let mut expr = register_base(ctx, reg);
611 if !register_is_scalar_source(ctx, reg) {
612 let swz = inst.source_swizzle(param_index);
613 let comp = swz[component.clamp(0, 3)];
614 expr.push_str(component_suffix(comp));
615 }
616 apply_source_modifier(expr, inst.source_modifier(param_index), 1)
617}
618
619fn register_is_scalar_source(ctx: &Context<'_>, reg: RegisterKey) -> bool {
620 match (reg, ctx.shader.kind) {
621 (
622 RegisterKey {
623 ty: RegisterType::ConstBool | RegisterType::Loop | RegisterType::Label,
624 ..
625 },
626 _,
627 ) => true,
628 (
629 RegisterKey {
630 ty: RegisterType::MiscType,
631 number: 1,
632 },
633 ShaderKind::Pixel,
634 ) => true,
635 _ => false,
636 }
637}
638fn register_base(ctx: &Context<'_>, reg: RegisterKey) -> String {
639 match (reg.ty, ctx.shader.kind) {
640 (RegisterType::Temp | RegisterType::TempFloat16 | RegisterType::Predicate, _) => {
641 temp_name(reg, ctx.shader.kind)
642 }
643 (RegisterType::Texture, ShaderKind::Vertex) => temp_name(reg, ctx.shader.kind),
644 (RegisterType::Texture | RegisterType::Input | RegisterType::MiscType, _) => {
645 format!("input.{}", input_field_name(reg))
646 }
647 (RegisterType::Const, _) => const_row_expr(ctx, reg.number),
648 (RegisterType::ConstInt, _) => int_const_expr(ctx, reg.number),
649 (RegisterType::ConstBool, _) => bool_const_expr(ctx, reg.number),
650 (RegisterType::Sampler, _) => format!("samp_s{}", reg.number),
651 (
652 RegisterType::ColorOut
653 | RegisterType::DepthOut
654 | RegisterType::RastOut
655 | RegisterType::AttrOut
656 | RegisterType::Output,
657 _,
658 ) => format!("output.{}", output_field_name(reg)),
659 (RegisterType::Loop, _) => "_loop".to_string(),
660 (RegisterType::Label, _) => format!("label{}", reg.number),
661 _ => format!("u{}", reg.number),
662 }
663}
664
665fn const_row_expr(ctx: &Context<'_>, index: u16) -> String {
666 if ctx.def_float.contains_key(&index) {
667 return format!("c{}", index);
668 }
669 format!("float_regs.c[{}]", index)
670}
671
672fn int_const_expr(ctx: &Context<'_>, index: u16) -> String {
673 if ctx.def_int.contains_key(&index) {
674 return format!("i{}", index);
675 }
676 format!("int_regs.i[{}]", index)
677}
678
679fn bool_const_expr(ctx: &Context<'_>, index: u16) -> String {
680 if ctx.def_bool.contains_key(&index) {
681 return format!("b{}", index);
682 }
683 format!("(bool_regs.b[{}] != 0u)", index)
684}
685
686fn analyze<'a>(shader: &'a ShaderModel, ctab: Option<&'a ConstantTable>) -> Context<'a> {
687 let mut ctx = Context {
688 shader,
689 ctab,
690 decls: BTreeMap::new(),
691 sampler_decls: BTreeMap::new(),
692 def_float: BTreeMap::new(),
693 def_int: BTreeMap::new(),
694 def_bool: BTreeMap::new(),
695 used_inputs: BTreeSet::new(),
696 used_outputs: BTreeSet::new(),
697 used_temps: BTreeSet::new(),
698 used_samplers: BTreeSet::new(),
699 used_consts: BTreeSet::new(),
700 used_int_consts: BTreeSet::new(),
701 used_bool_consts: BTreeSet::new(),
702 uses_lit: false,
703 uses_dst: false,
704 };
705
706 for inst in &shader.instructions {
707 match inst.opcode {
708 Opcode::Dcl => {
709 if let Some(reg) = inst.dest_register() {
710 if reg.ty == RegisterType::Sampler {
711 ctx.sampler_decls
712 .insert(reg.number, inst.decl_sampler_type());
713 ctx.used_samplers.insert(reg.number);
714 } else {
715 let semantic = semantic_from_decl(shader.kind, inst);
716 ctx.decls.insert(reg, DeclInfo { reg, semantic });
717 }
718 }
719 }
720 Opcode::Def => {
721 if let Some(reg) = inst.dest_register()
722 && reg.ty == RegisterType::Const
723 {
724 ctx.def_float.insert(
725 reg.number,
726 DefFloat {
727 values: [
728 inst.get_float_param(1),
729 inst.get_float_param(2),
730 inst.get_float_param(3),
731 inst.get_float_param(4),
732 ],
733 },
734 );
735 }
736 }
737 Opcode::DefI => {
738 if let Some(reg) = inst.dest_register()
739 && reg.ty == RegisterType::ConstInt
740 {
741 ctx.def_int.insert(
742 reg.number,
743 DefInt {
744 values: [
745 inst.get_int_param(1),
746 inst.get_int_param(2),
747 inst.get_int_param(3),
748 inst.get_int_param(4),
749 ],
750 },
751 );
752 }
753 }
754 Opcode::DefB => {
755 if let Some(reg) = inst.dest_register()
756 && reg.ty == RegisterType::ConstBool
757 {
758 ctx.def_bool.insert(
759 reg.number,
760 DefBool {
761 value: inst.get_int_param(1) != 0,
762 },
763 );
764 }
765 }
766 Opcode::Lit => ctx.uses_lit = true,
767 Opcode::Dst => ctx.uses_dst = true,
768 _ => {}
769 }
770
771 if inst.opcode == Opcode::End || inst.opcode == Opcode::Comment {
772 continue;
773 }
774
775 if inst.opcode == Opcode::TexKill {
776 if let Some(reg) = inst.dest_register() {
777 classify_source_register(&mut ctx, reg);
778 }
779 continue;
780 }
781
782 if inst.opcode.has_destination()
783 && let Some(reg) = inst.dest_register()
784 {
785 classify_dest_register(&mut ctx, reg);
786 }
787
788 let first_src = if inst.opcode.has_destination() { 1 } else { 0 };
789 for pi in first_src..inst.params.len() {
790 if matches!(
791 inst.opcode,
792 Opcode::Dcl | Opcode::Def | Opcode::DefI | Opcode::DefB
793 ) {
794 continue;
795 }
796 if let Some(reg) = inst.source_register(pi) {
797 classify_source_register(&mut ctx, reg);
798 }
799 }
800 }
801
802 infer_missing_decls(&mut ctx);
803 ctx
804}
805
806fn classify_dest_register(ctx: &mut Context<'_>, reg: RegisterKey) {
807 match reg.ty {
808 RegisterType::Temp
809 | RegisterType::TempFloat16
810 | RegisterType::Texture
811 | RegisterType::Predicate => {
812 if !(ctx.shader.kind == ShaderKind::Pixel && reg.ty == RegisterType::Texture) {
813 ctx.used_temps.insert(reg);
814 }
815 }
816 RegisterType::RastOut
817 | RegisterType::AttrOut
818 | RegisterType::Output
819 | RegisterType::ColorOut
820 | RegisterType::DepthOut => {
821 ctx.used_outputs.insert(reg);
822 }
823 _ => {}
824 }
825}
826
827fn classify_source_register(ctx: &mut Context<'_>, reg: RegisterKey) {
828 match reg.ty {
829 RegisterType::Input | RegisterType::MiscType => {
830 ctx.used_inputs.insert(reg);
831 }
832 RegisterType::Texture => {
833 if ctx.shader.kind == ShaderKind::Pixel {
834 ctx.used_inputs.insert(reg);
835 } else {
836 ctx.used_temps.insert(reg);
837 }
838 }
839 RegisterType::Temp | RegisterType::TempFloat16 | RegisterType::Predicate => {
840 ctx.used_temps.insert(reg);
841 }
842 RegisterType::Const => {
843 ctx.used_consts.insert(reg.number);
844 }
845 RegisterType::ConstInt => {
846 ctx.used_int_consts.insert(reg.number);
847 }
848 RegisterType::ConstBool => {
849 ctx.used_bool_consts.insert(reg.number);
850 }
851 RegisterType::Sampler => {
852 ctx.used_samplers.insert(reg.number);
853 }
854 RegisterType::RastOut
855 | RegisterType::AttrOut
856 | RegisterType::Output
857 | RegisterType::ColorOut
858 | RegisterType::DepthOut => {
859 ctx.used_outputs.insert(reg);
860 }
861 _ => {}
862 }
863}
864
865fn infer_missing_decls(ctx: &mut Context<'_>) {
866 for reg in ctx.used_inputs.clone() {
867 if ctx.decls.contains_key(®) {
868 continue;
869 }
870 let semantic = inferred_input_semantic(ctx.shader.kind, reg);
871 ctx.decls.insert(reg, DeclInfo { reg, semantic });
872 }
873 for sampler in ctx.used_samplers.clone() {
874 ctx.sampler_decls
875 .entry(sampler)
876 .or_insert(SamplerTextureType::TwoD);
877 }
878 if ctx.shader.kind == ShaderKind::Pixel && ctx.used_outputs.is_empty() {
879 ctx.used_outputs.insert(RegisterKey {
880 ty: RegisterType::ColorOut,
881 number: 0,
882 });
883 }
884}
885
886fn semantic_from_decl(kind: ShaderKind, inst: &Instruction) -> String {
887 if let Some(reg) = inst.dest_register() {
888 if reg.ty == RegisterType::MiscType {
889 return match reg.number {
890 0 => "VPOS".to_string(),
891 1 => "VFACE".to_string(),
892 _ => format!("TEXCOORD{}", reg.number),
893 };
894 }
895 if kind == ShaderKind::Pixel {
896 match reg.ty {
897 RegisterType::Texture => return format!("TEXCOORD{}", reg.number),
898 RegisterType::Input => return format!("COLOR{}", reg.number),
899 _ => {}
900 }
901 }
902 }
903 let usage = inst.decl_usage();
904 let index = inst.decl_index();
905 let prefix = usage.semantic_prefix();
906 if index == 0
907 && !matches!(
908 usage,
909 DeclUsage::TexCoord | DeclUsage::Color | DeclUsage::Position
910 )
911 {
912 prefix.to_string()
913 } else {
914 format!("{}{}", prefix, index)
915 }
916}
917
918fn inferred_input_semantic(kind: ShaderKind, reg: RegisterKey) -> String {
919 match (kind, reg.ty) {
920 (ShaderKind::Pixel, RegisterType::Texture) => format!("TEXCOORD{}", reg.number),
921 (ShaderKind::Pixel, RegisterType::Input) => format!("COLOR{}", reg.number),
922 (_, RegisterType::Input) => match reg.number {
923 0 => "POSITION0".to_string(),
924 1 => "NORMAL0".to_string(),
925 n => format!("TEXCOORD{}", n.saturating_sub(2)),
926 },
927 (_, RegisterType::MiscType) => match reg.number {
928 0 => "VPOS".to_string(),
929 1 => "VFACE".to_string(),
930 _ => format!("TEXCOORD{}", reg.number),
931 },
932 _ => format!("TEXCOORD{}", reg.number),
933 }
934}
935
936fn output_semantic(ctx: &Context<'_>, reg: RegisterKey) -> String {
937 if let Some(decl) = ctx.decls.get(®) {
938 return decl.semantic.clone();
939 }
940 match reg.ty {
941 RegisterType::ColorOut => format!("COLOR{}", reg.number),
942 RegisterType::DepthOut => "DEPTH".to_string(),
943 RegisterType::RastOut => match reg.number {
944 0 => "POSITION0".to_string(),
945 1 => "FOG".to_string(),
946 2 => "PSIZE".to_string(),
947 n => format!("TEXCOORD{}", n),
948 },
949 RegisterType::AttrOut => format!("COLOR{}", reg.number),
950 RegisterType::Output => format!("TEXCOORD{}", reg.number),
951 _ => format!("TEXCOORD{}", reg.number),
952 }
953}
954
955fn input_attr(kind: ShaderKind, semantic: &str) -> String {
956 let upper = semantic.to_ascii_uppercase();
957 if kind == ShaderKind::Pixel && (upper == "VPOS" || upper == "POSITION" || upper == "POSITION0")
958 {
959 return "@builtin(position)".to_string();
960 }
961 if kind == ShaderKind::Pixel && upper == "VFACE" {
962 return "@builtin(front_facing)".to_string();
963 }
964 format!("@location({})", semantic_location(semantic))
965}
966fn output_attr(kind: ShaderKind, semantic: &str, reg: RegisterKey) -> String {
967 if kind == ShaderKind::Vertex && is_position_semantic(semantic) {
968 "@builtin(position)".to_string()
969 } else if reg.ty == RegisterType::DepthOut || semantic == "DEPTH" {
970 "@builtin(frag_depth)".to_string()
971 } else {
972 format!("@location({})", semantic_location(semantic))
973 }
974}
975
976fn semantic_location(semantic: &str) -> u32 {
977 let upper = semantic.to_ascii_uppercase();
978 if let Some(n) = parse_semantic_index(&upper, "POSITION") {
979 return n;
980 }
981 if let Some(n) = parse_semantic_index(&upper, "NORMAL") {
982 return 1 + n;
983 }
984 if let Some(n) = parse_semantic_index(&upper, "COLOR") {
985 return 2 + n;
986 }
987 if let Some(n) = parse_semantic_index(&upper, "TEXCOORD") {
988 return 4 + n;
989 }
990 if let Some(n) = parse_semantic_index(&upper, "BLENDWEIGHT") {
991 return 12 + n;
992 }
993 if let Some(n) = parse_semantic_index(&upper, "BLENDINDICES") {
994 return 14 + n;
995 }
996 if let Some(n) = parse_semantic_index(&upper, "TANGENT") {
997 return 16 + n;
998 }
999 if let Some(n) = parse_semantic_index(&upper, "BINORMAL") {
1000 return 18 + n;
1001 }
1002 if upper == "FOG" {
1003 return 20;
1004 }
1005 if upper == "PSIZE" {
1006 return 21;
1007 }
1008 31
1009}
1010
1011fn parse_semantic_index(s: &str, prefix: &str) -> Option<u32> {
1012 if !s.starts_with(prefix) {
1013 return None;
1014 }
1015 let rest = &s[prefix.len()..];
1016 if rest.is_empty() {
1017 Some(0)
1018 } else {
1019 rest.parse::<u32>().ok()
1020 }
1021}
1022
1023fn is_position_semantic(s: &str) -> bool {
1024 let upper = s.to_ascii_uppercase();
1025 upper == "POSITION" || upper == "POSITION0" || upper == "POSITIONT" || upper == "POSITIONT0"
1026}
1027
1028fn wgsl_input_field_type(ctx: &Context<'_>, reg: RegisterKey) -> &'static str {
1029 match reg {
1030 RegisterKey {
1031 ty: RegisterType::MiscType,
1032 number: 1,
1033 } => "bool",
1034 _ => {
1035 if let Some(decl) = ctx.decls.get(®)
1036 && (decl.semantic.starts_with("TEXCOORD") || decl.semantic.starts_with("COLOR"))
1037 {
1038 return "vec4<f32>";
1039 }
1040 "vec4<f32>"
1041 }
1042 }
1043}
1044
1045fn wgsl_output_field_type(reg: RegisterKey) -> &'static str {
1046 match reg {
1047 RegisterKey {
1048 ty: RegisterType::DepthOut,
1049 ..
1050 } => "f32",
1051 RegisterKey {
1052 ty: RegisterType::RastOut,
1053 number: 1 | 2,
1054 } => "f32",
1055 _ => "vec4<f32>",
1056 }
1057}
1058
1059fn input_struct_name(kind: ShaderKind) -> &'static str {
1060 match kind {
1061 ShaderKind::Vertex => "VSInput",
1062 ShaderKind::Pixel => "PSInput",
1063 }
1064}
1065
1066fn output_struct_name(kind: ShaderKind) -> &'static str {
1067 match kind {
1068 ShaderKind::Vertex => "VSOutput",
1069 ShaderKind::Pixel => "PSOutput",
1070 }
1071}
1072
1073fn input_field_name(reg: RegisterKey) -> String {
1074 match reg.ty {
1075 RegisterType::Input => format!("v{}", reg.number),
1076 RegisterType::Texture => format!("t{}", reg.number),
1077 RegisterType::MiscType => match reg.number {
1078 0 => "vPos".to_string(),
1079 1 => "vFace".to_string(),
1080 _ => format!("vMisc{}", reg.number),
1081 },
1082 _ => format!("in{}", reg.number),
1083 }
1084}
1085
1086fn output_field_name(reg: RegisterKey) -> String {
1087 match reg.ty {
1088 RegisterType::ColorOut => format!("oC{}", reg.number),
1089 RegisterType::DepthOut => "oDepth".to_string(),
1090 RegisterType::RastOut => match reg.number {
1091 0 => "oPos".to_string(),
1092 1 => "oFog".to_string(),
1093 2 => "oPts".to_string(),
1094 _ => format!("o{}", reg.number),
1095 },
1096 RegisterType::AttrOut => format!("oD{}", reg.number),
1097 RegisterType::Output => format!("o{}", reg.number),
1098 _ => format!("out{}", reg.number),
1099 }
1100}
1101
1102fn temp_name(reg: RegisterKey, kind: ShaderKind) -> String {
1103 match (reg.ty, kind) {
1104 (RegisterType::Texture, ShaderKind::Vertex) => format!("a{}", reg.number),
1105 (RegisterType::Predicate, _) => format!("p{}", reg.number),
1106 (RegisterType::TempFloat16, _) => format!("h{}", reg.number),
1107 _ => format!("r{}", reg.number),
1108 }
1109}
1110
1111fn temp_type(reg: RegisterKey) -> &'static str {
1112 match reg.ty {
1113 RegisterType::Predicate => "vec4<bool>",
1114 RegisterType::Texture => "vec4<f32>",
1115 RegisterType::TempFloat16 => "vec4<f32>",
1116 _ => "vec4<f32>",
1117 }
1118}
1119
1120fn zero_value(ty: &str) -> &'static str {
1121 match ty {
1122 "f32" => "0.0",
1123 "bool" => "false",
1124 "vec2<f32>" => "vec2<f32>(0.0)",
1125 "vec3<f32>" => "vec3<f32>(0.0)",
1126 "vec4<bool>" => "vec4<bool>(false)",
1127 _ => "vec4<f32>(0.0)",
1128 }
1129}
1130
1131fn zero_vector(n: usize) -> String {
1132 match n {
1133 1 => "0.0".to_string(),
1134 2 => "vec2<f32>(0.0)".to_string(),
1135 3 => "vec3<f32>(0.0)".to_string(),
1136 _ => "vec4<f32>(0.0)".to_string(),
1137 }
1138}
1139
1140fn one_vector(n: usize) -> String {
1141 match n {
1142 1 => "1.0".to_string(),
1143 2 => "vec2<f32>(1.0)".to_string(),
1144 3 => "vec3<f32>(1.0)".to_string(),
1145 _ => "vec4<f32>(1.0)".to_string(),
1146 }
1147}
1148
1149fn half_vector(n: usize) -> String {
1150 match n {
1151 1 => "0.5".to_string(),
1152 2 => "vec2<f32>(0.5)".to_string(),
1153 3 => "vec3<f32>(0.5)".to_string(),
1154 _ => "vec4<f32>(0.5)".to_string(),
1155 }
1156}
1157
1158fn vector_constructor(values: &[String]) -> String {
1159 match values.len() {
1160 0 => "0.0".to_string(),
1161 1 => values[0].clone(),
1162 2 => format!("vec2<f32>({}, {})", values[0], values[1]),
1163 3 => format!("vec3<f32>({}, {}, {})", values[0], values[1], values[2]),
1164 _ => format!(
1165 "vec4<f32>({}, {}, {}, {})",
1166 values[0], values[1], values[2], values[3]
1167 ),
1168 }
1169}
1170
1171fn wgsl_mask_suffix(mask: u8) -> String {
1172 let m = if mask == 0 { 0xf } else { mask };
1173 if m == 0xf {
1174 String::new()
1175 } else {
1176 let mut s = String::from(".");
1177 for (i, c) in ['x', 'y', 'z', 'w'].iter().enumerate() {
1178 if (m & (1 << i)) != 0 {
1179 s.push(*c);
1180 }
1181 }
1182 s
1183 }
1184}
1185
1186fn wgsl_source_swizzle_suffix(swizzle: [usize; 4], count: usize) -> String {
1187 let count = count.clamp(1, 4);
1188 let identity = [0usize, 1, 2, 3];
1189 if count == 4 && swizzle == identity {
1190 return String::new();
1191 }
1192 let names = ['x', 'y', 'z', 'w'];
1193 let mut s = String::from(".");
1194 for i in 0..count {
1195 s.push(names[swizzle[i]]);
1196 }
1197 s
1198}
1199
1200fn component_suffix(component: usize) -> &'static str {
1201 match component {
1202 0 => ".x",
1203 1 => ".y",
1204 2 => ".z",
1205 3 => ".w",
1206 _ => ".x",
1207 }
1208}
1209
1210fn coerce_expr_width(expr: String, src_width: usize, dst_width: usize) -> String {
1211 let src_width = src_width.clamp(1, 4);
1212 let dst_width = dst_width.clamp(1, 4);
1213 if src_width == dst_width {
1214 return expr;
1215 }
1216 if src_width == 1 {
1217 return match dst_width {
1218 1 => expr,
1219 2 => format!("vec2<f32>({})", expr),
1220 3 => format!("vec3<f32>({})", expr),
1221 _ => format!("vec4<f32>({})", expr),
1222 };
1223 }
1224 if src_width > dst_width {
1225 let suffix = match dst_width {
1226 1 => ".x",
1227 2 => ".xy",
1228 3 => ".xyz",
1229 _ => "",
1230 };
1231 if suffix.is_empty() {
1232 expr
1233 } else {
1234 format!("({}){}", expr, suffix)
1235 }
1236 } else {
1237 match dst_width {
1238 2 => format!("vec2<f32>({}, 0.0)", expr),
1239 3 => format!("vec3<f32>({}, 0.0)", expr),
1240 _ => format!("vec4<f32>({}, 0.0)", expr),
1241 }
1242 }
1243}
1244fn apply_source_modifier(expr: String, modifier: SourceModifier, count: usize) -> String {
1245 match modifier {
1246 SourceModifier::None => expr,
1247 SourceModifier::Negate => format!("-({})", expr),
1248 SourceModifier::Bias => format!("(({}) - {})", expr, half_vector(count)),
1249 SourceModifier::BiasAndNegate => format!("-(({}) - {})", expr, half_vector(count)),
1250 SourceModifier::Sign => format!(
1251 "((({}) - {}) * {})",
1252 expr,
1253 half_vector(count),
1254 one_vector(count)
1255 ),
1256 SourceModifier::SignAndNegate => format!(
1257 "-((({}) - {}) * {})",
1258 expr,
1259 half_vector(count),
1260 one_vector(count)
1261 ),
1262 SourceModifier::Complement => format!("({} - ({}))", one_vector(count), expr),
1263 SourceModifier::X2 => format!("(({}) * 2.0)", expr),
1264 SourceModifier::X2AndNegate => format!("-(({}) * 2.0)", expr),
1265 SourceModifier::DivideByZ => format!("(({}) / ({}).z)", expr, expr),
1266 SourceModifier::DivideByW => format!("(({}) / ({}).w)", expr, expr),
1267 SourceModifier::Abs => format!("abs({})", expr),
1268 SourceModifier::AbsAndNegate => format!("-abs({})", expr),
1269 SourceModifier::Not => format!("!({})", expr),
1270 SourceModifier::Unknown(_) => expr,
1271 }
1272}
1273
1274fn apply_result_modifier(expr: String, modifier: ResultModifier, count: usize) -> String {
1275 if modifier.saturate {
1276 format!(
1277 "clamp({}, {}, {})",
1278 expr,
1279 zero_vector(count),
1280 one_vector(count)
1281 )
1282 } else {
1283 expr
1284 }
1285}
1286
1287fn scalar_bool_expr(expr: String) -> String {
1288 format!("({} != 0.0)", expr)
1289}
1290
1291fn line(out: &mut String, indent: usize, s: &str) {
1292 for _ in 0..indent {
1293 out.push_str(" ");
1294 }
1295 out.push_str(s);
1296 out.push('\n');
1297}
1298
1299fn compare_all_expr(a: String, op: &str, b: String, width: usize) -> String {
1300 if width <= 1 {
1301 format!("{} {} {}", a, op, b)
1302 } else {
1303 format!("all({} {} {})", a, op, b)
1304 }
1305}
1306
1307fn cmp_op(code: u8) -> &'static str {
1308 match code {
1309 1 => ">",
1310 2 => "==",
1311 3 => ">=",
1312 4 => "<",
1313 5 => "!=",
1314 6 => "<=",
1315 _ => "!=",
1316 }
1317}
1318
1319fn sampler_texture_type(ctx: &Context<'_>, sampler: u16) -> SamplerTextureType {
1320 if let Some(c) = sampler_constant(ctx, sampler)
1321 && let Some(t) = &c.type_info
1322 {
1323 return match t.value_type {
1324 ValueType::SamplerCube => SamplerTextureType::Cube,
1325 ValueType::Sampler3D => SamplerTextureType::Volume,
1326 _ => SamplerTextureType::TwoD,
1327 };
1328 }
1329 ctx.sampler_decls
1330 .get(&sampler)
1331 .copied()
1332 .unwrap_or(SamplerTextureType::TwoD)
1333}
1334
1335fn wgsl_texture_type(ty: SamplerTextureType) -> &'static str {
1336 match ty {
1337 SamplerTextureType::Cube => "texture_cube<f32>",
1338 SamplerTextureType::Volume => "texture_3d<f32>",
1339 SamplerTextureType::TwoD | SamplerTextureType::Unknown => "texture_2d<f32>",
1340 }
1341}
1342
1343fn sampler_constant<'a>(ctx: &'a Context<'_>, sampler: u16) -> Option<&'a ConstantInfo> {
1344 ctx.ctab?
1345 .constants
1346 .iter()
1347 .find(|c| c.register_set == RegisterSet::Sampler && c.register_index == sampler)
1348}
1349
1350fn fmt_f32(v: f32) -> String {
1351 if v.is_nan() {
1352 "(0.0 / 0.0)".to_string()
1353 } else if v.is_infinite() {
1354 if v.is_sign_positive() {
1355 "(1.0 / 0.0)".to_string()
1356 } else {
1357 "(-1.0 / 0.0)".to_string()
1358 }
1359 } else {
1360 let mut s = format!("{:.9}", v);
1361 while s.contains('.') && s.ends_with('0') {
1362 s.pop();
1363 }
1364 if s.ends_with('.') {
1365 s.push('0');
1366 }
1367 if !s.contains('.') && !s.contains('e') && !s.contains('E') {
1368 s.push_str(".0");
1369 }
1370 s
1371 }
1372}