1use crate::codegen::CodegenCtx;
4use crate::codegen::function_emitter::FunctionEmitter;
5use crate::codegen::function_emitter::expr::{emit_expr, emit_vtable_dispatch_on_object_stack};
6use crate::codegen::intrinsics::IntrinsicTypeIndices;
7use crate::codegen::symbol_table::may_hold_null;
8use crate::{ExprId, Type};
9use wasm_encoder::{BlockType, Function, HeapType, Instruction, RefType, ValType};
10
11pub(super) fn emit_stringify(
12 emitter: &mut FunctionEmitter,
13 ctx: &CodegenCtx,
14 args: &[ExprId],
15 result_ty: &Type,
16) -> Result<(), crate::compiler_error::CompilerFailure> {
17 let [arg, rest @ ..] = args else {
18 return Err(crate::codegen::internal_failure(
19 "JSON.stringify requires a value",
20 ));
21 };
22 if rest.len() > 2 {
23 return Err(crate::codegen::internal_failure(
24 "JSON.stringify accepts at most three arguments",
25 ));
26 }
27 let arg = *arg;
28 let arg_ty = ctx
29 .ta
30 .try_expr(arg)
31 .map_err(crate::codegen::arena_failure)?
32 .ty
33 .clone();
34 if args.len() == 1 {
35 emit_expr(emitter, ctx, arg)?;
36 emit_stringify_value(emitter, ctx, &arg_ty);
37 } else {
38 emit_expr(emitter, ctx, arg)?;
39 let arg_local = emitter.add_anonymous_local(ctx.symbols.value_type(&arg_ty)?)?;
40 emitter.instruction(Instruction::LocalSet(arg_local));
41 emit_stringify_optional_args(emitter, ctx, args, arg_local, &arg_ty)?;
42 }
43 let emitted_ty = if may_stringify_undefined(&arg_ty) {
46 Type::union(vec![Type::String, Type::Undefined])
47 } else {
48 Type::String
49 };
50 super::cast::emit_coerce_to_slot(emitter, ctx, &emitted_ty, result_ty)
51}
52
53pub(crate) fn emit_stringify_value(emitter: &mut FunctionEmitter, ctx: &CodegenCtx, arg_ty: &Type) {
58 if !may_stringify_undefined(arg_ty) {
59 emit_defined_stringify_value(emitter, ctx, arg_ty);
60 return;
61 }
62 let Some(intrinsics) = ctx.require(
63 ctx.symbols.intrinsic_type_indices(),
64 "intrinsics registered",
65 ) else {
66 return;
67 };
68 let Some(value) = ctx.latch(emitter.add_anonymous_local(ValType::Ref(RefType {
69 nullable: true,
70 heap_type: HeapType::Concrete(intrinsics.object),
71 }))) else {
72 return;
73 };
74 emitter.instruction(Instruction::LocalSet(value));
75 emitter.instructions(crate::codegen::nullish::is_undefined_or_closure(
76 &[Instruction::LocalGet(value)],
77 intrinsics,
78 ));
79 emitter.emit_if(BlockType::Result(ValType::Ref(RefType {
80 nullable: false,
81 heap_type: HeapType::Concrete(intrinsics.object),
82 })));
83 if ctx
84 .latch(super::expr::emit_undefined(emitter, ctx))
85 .is_none()
86 {
87 return;
88 }
89 emitter.emit_else();
90 emitter.instruction(Instruction::LocalGet(value));
91 emit_stringify_nullable(emitter, ctx);
92 emitter.emit_end();
93}
94
95fn may_stringify_undefined(ty: &Type) -> bool {
96 ty.any_member(&Type::stringifies_to_undefined)
97}
98
99fn emit_defined_stringify_value(emitter: &mut FunctionEmitter, ctx: &CodegenCtx, arg_ty: &Type) {
100 let peeled = match arg_ty.peel() {
104 Type::Union(members) if is_unboxed_number_union(members) => &Type::Number,
105 peeled => peeled,
106 };
107 match peeled {
108 Type::Null => {
109 emitter.instruction(Instruction::Drop);
111 ctx.latch(super::emit_inline_string_literal(emitter, ctx, "null"));
112 }
113 Type::Boolean | Type::BooleanLiteral(_) => emit_to_json_direct(emitter, ctx, "Boolean"),
114 Type::Number | Type::NumberLiteral(_) => {
115 emit_to_json_direct(emitter, ctx, "Number");
116 }
117 Type::String | Type::StringLiteral(_) => {
118 emit_stringify_string_host(emitter, ctx);
119 }
120 _ if may_hold_null(peeled) => {
121 emit_stringify_nullable(emitter, ctx);
125 }
126 _ => {
127 emit_vtable_dispatch_on_object_stack(emitter, ctx, 1);
128 }
129 }
130}
131
132fn is_unboxed_number_union(members: &[Type]) -> bool {
135 !members.is_empty()
136 && members
137 .iter()
138 .all(|member| matches!(member.peel(), Type::Number | Type::NumberLiteral(_)))
139}
140
141fn emit_stringify_nullable(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) {
142 let Some(intrinsics) = ctx.require(
143 ctx.symbols.intrinsic_type_indices(),
144 "intrinsics declared by codegen entry",
145 ) else {
146 return;
147 };
148 let Some(obj_tmp) = ctx.latch(emitter.add_anonymous_local(ValType::Ref(RefType {
149 nullable: true,
150 heap_type: HeapType::Concrete(intrinsics.object),
151 }))) else {
152 return;
153 };
154 emitter.instruction(Instruction::LocalTee(obj_tmp));
155 emitter.instruction(Instruction::RefIsNull);
156 emitter.emit_if(BlockType::Result(ValType::Ref(RefType {
157 nullable: false,
158 heap_type: HeapType::Concrete(intrinsics.string),
159 })));
160 ctx.latch(super::emit_inline_string_literal(emitter, ctx, "null"));
161 emitter.emit_else();
162 emitter.instruction(Instruction::LocalGet(obj_tmp));
163 emitter.instruction(Instruction::RefAsNonNull);
164 emit_vtable_dispatch_on_object_stack(emitter, ctx, 1);
165 emitter.emit_end();
166}
167
168fn emit_stringify_string_host(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) {
173 let Some(intrinsics) = ctx.require(
174 ctx.symbols.intrinsic_type_indices(),
175 "intrinsic type indices registered",
176 ) else {
177 return;
178 };
179 let Some(string_type_idx) = ctx.require(ctx.symbols.string_type_idx(), "$string registered")
180 else {
181 return;
182 };
183 emitter.instruction(Instruction::StructGet {
184 struct_type_index: string_type_idx,
185 field_index: 1,
186 });
187
188 let Some(stringify_idx) = ctx.require(
189 ctx.symbols.func_idx(&crate::mangle::host(
190 crate::runtime::JSON_MODULE_NAME,
191 "stringify",
192 )),
193 "submilli:json.stringify imported during codegen",
194 ) else {
195 return;
196 };
197 emitter.instruction(Instruction::Call(stringify_idx));
198
199 let Some(raw_local) = ctx.latch(emitter.add_anonymous_local(ValType::Ref(RefType {
200 nullable: false,
201 heap_type: HeapType::Concrete(intrinsics.raw_string),
202 }))) else {
203 return;
204 };
205 emitter.instruction(Instruction::LocalSet(raw_local));
206 let Some(string_vtable_idx) = ctx.require(
207 ctx.symbols.prelude_global_idx("string_vtable"),
208 "string_vtable imported",
209 ) else {
210 return;
211 };
212 emitter.instruction(Instruction::GlobalGet(string_vtable_idx));
213 emitter.instruction(Instruction::LocalGet(raw_local));
214 emitter.instruction(Instruction::I64Const(0));
215 emitter.instruction(Instruction::StructNew(intrinsics.string));
216}
217
218fn emit_stringify_optional_args(
219 emitter: &mut FunctionEmitter,
220 ctx: &CodegenCtx,
221 args: &[ExprId],
222 arg_local: u32,
223 arg_ty: &Type,
224) -> Result<(), crate::compiler_error::CompilerFailure> {
225 let [_, replacer, rest @ ..] = args else {
226 return Err(crate::codegen::internal_failure(
227 "JSON.stringify optional arguments are missing",
228 ));
229 };
230 emit_expr(emitter, ctx, *replacer)?;
231 emitter.instruction(Instruction::Drop);
232
233 let space = match rest {
234 [] => StringifySpace::None,
235 [space] => emit_stringify_space_arg(emitter, ctx, *space)?,
236 _ => {
237 return Err(crate::codegen::internal_failure(
238 "JSON.stringify has extra optional arguments",
239 ));
240 }
241 };
242
243 emitter.instruction(Instruction::LocalGet(arg_local));
244 emit_stringify_value(emitter, ctx, arg_ty);
245 let may_be_undefined = may_stringify_undefined(arg_ty);
246 if may_be_undefined {
247 let intr = ctx
248 .symbols
249 .intrinsic_type_indices()
250 .ok_or_else(|| crate::codegen::internal_failure("intrinsics registered"))?;
251 let result_type = ValType::Ref(RefType {
252 nullable: false,
253 heap_type: HeapType::Concrete(intr.object),
254 });
255 let result = emitter.add_anonymous_local(result_type)?;
256 emitter.instruction(Instruction::LocalTee(result));
257 emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(
258 intr.undefined,
259 )));
260 emitter.emit_if(BlockType::Result(result_type));
261 emitter.instruction(Instruction::LocalGet(result));
262 emitter.emit_else();
263 emitter.instruction(Instruction::LocalGet(result));
264 emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(intr.string)));
265 }
266 match space {
267 StringifySpace::None => {}
268 StringifySpace::Dynamic(local) => emit_dynamic_space(emitter, ctx, local)?,
269 StringifySpace::Number(local) => {
270 emit_string_on_stack_raw(emitter, ctx);
271 emitter.instruction(Instruction::LocalGet(local));
272 emit_pretty_number_host(emitter, ctx);
273 emit_wrap_raw_string(emitter, ctx);
274 }
275 StringifySpace::String(local) => {
276 emit_string_on_stack_raw(emitter, ctx);
277 emitter.instruction(Instruction::LocalGet(local));
278 emit_string_on_stack_raw(emitter, ctx);
279 emit_pretty_string_host(emitter, ctx);
280 emit_wrap_raw_string(emitter, ctx);
281 }
282 };
283 if may_be_undefined {
284 emitter.emit_end();
285 }
286 Ok(())
287}
288
289enum StringifySpace {
290 None,
291 Number(u32),
292 String(u32),
293 Dynamic(u32),
294}
295
296fn emit_stringify_space_arg(
297 emitter: &mut FunctionEmitter,
298 ctx: &CodegenCtx,
299 space: ExprId,
300) -> Result<StringifySpace, crate::compiler_error::CompilerFailure> {
301 let space_ty = ctx
302 .ta
303 .try_expr(space)
304 .map_err(crate::codegen::arena_failure)?
305 .ty
306 .clone();
307 Ok(match space_ty.peel() {
308 Type::Number | Type::NumberLiteral(_) => {
309 emit_expr(emitter, ctx, space)?;
310 let local = emitter.add_anonymous_local(ValType::F64)?;
311 emitter.instruction(Instruction::LocalSet(local));
312 StringifySpace::Number(local)
313 }
314 Type::String | Type::StringLiteral(_) => {
315 emit_expr(emitter, ctx, space)?;
316 let local = emitter.add_anonymous_local(ctx.symbols.value_type(&space_ty)?)?;
317 emitter.instruction(Instruction::LocalSet(local));
318 StringifySpace::String(local)
319 }
320 Type::Null | Type::Undefined | Type::Void => {
321 emit_expr(emitter, ctx, space)?;
322 emitter.instruction(Instruction::Drop);
323 StringifySpace::None
324 }
325 Type::Unknown | Type::Union(_) => {
326 emit_expr(emitter, ctx, space)?;
327 let local = emitter.add_anonymous_local(ctx.symbols.value_type(&Type::Unknown)?)?;
328 emitter.instruction(Instruction::LocalSet(local));
329 StringifySpace::Dynamic(local)
330 }
331 other => {
332 return Err(crate::codegen::internal_failure(format!(
333 "JSON.stringify space type was not validated: {other}"
334 )));
335 }
336 })
337}
338
339fn emit_dynamic_space(
340 emitter: &mut FunctionEmitter,
341 ctx: &CodegenCtx,
342 space: u32,
343) -> Result<(), crate::compiler_error::CompilerFailure> {
344 let string_type = ctx.symbols.value_type(&Type::String)?;
345 let json = emitter.add_anonymous_local(string_type)?;
346 emitter.instruction(Instruction::LocalSet(json));
347 let number = ctx
348 .symbols
349 .boxed_number_type_idx()
350 .ok_or_else(|| crate::codegen::internal_failure("boxed number registered"))?;
351 let string = ctx
352 .symbols
353 .string_type_idx()
354 .ok_or_else(|| crate::codegen::internal_failure("string registered"))?;
355 emitter.instruction(Instruction::LocalGet(space));
356 emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(number)));
357 emitter.emit_if(BlockType::Result(string_type));
358 emitter.instruction(Instruction::LocalGet(json));
359 emit_string_on_stack_raw(emitter, ctx);
360 emitter.instruction(Instruction::LocalGet(space));
361 super::cast::emit_cast_to(emitter, ctx, &Type::Number)?;
362 emit_pretty_number_host(emitter, ctx);
363 emit_wrap_raw_string(emitter, ctx);
364 emitter.emit_else();
365 emitter.instruction(Instruction::LocalGet(space));
366 emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(string)));
367 emitter.emit_if(BlockType::Result(string_type));
368 emitter.instruction(Instruction::LocalGet(json));
369 emit_string_on_stack_raw(emitter, ctx);
370 emitter.instruction(Instruction::LocalGet(space));
371 super::cast::emit_cast_to(emitter, ctx, &Type::String)?;
372 emit_string_on_stack_raw(emitter, ctx);
373 emit_pretty_string_host(emitter, ctx);
374 emit_wrap_raw_string(emitter, ctx);
375 emitter.emit_else();
376 emitter.instruction(Instruction::LocalGet(json));
377 emitter.emit_end();
378 emitter.emit_end();
379
380 Ok(())
381}
382
383fn emit_string_on_stack_raw(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) {
384 let Some(string_type_idx) = ctx.require(ctx.symbols.string_type_idx(), "$string registered")
385 else {
386 return;
387 };
388 emitter.instruction(Instruction::StructGet {
389 struct_type_index: string_type_idx,
390 field_index: 1,
391 });
392}
393
394fn emit_pretty_number_host(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) {
395 let Some(pretty_idx) = ctx.require(
396 ctx.symbols.func_idx(&crate::mangle::host(
397 crate::runtime::JSON_MODULE_NAME,
398 "stringifyPrettyNumber",
399 )),
400 "submilli:json.stringifyPrettyNumber imported during codegen",
401 ) else {
402 return;
403 };
404 emitter.instruction(Instruction::Call(pretty_idx));
405}
406
407fn emit_pretty_string_host(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) {
408 let Some(pretty_idx) = ctx.require(
409 ctx.symbols.func_idx(&crate::mangle::host(
410 crate::runtime::JSON_MODULE_NAME,
411 "stringifyPrettyString",
412 )),
413 "submilli:json.stringifyPrettyString imported during codegen",
414 ) else {
415 return;
416 };
417 emitter.instruction(Instruction::Call(pretty_idx));
418}
419
420fn emit_wrap_raw_string(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) {
421 let Some(intrinsics) = ctx.require(
422 ctx.symbols.intrinsic_type_indices(),
423 "intrinsic type indices registered",
424 ) else {
425 return;
426 };
427 let Some(raw_local) = ctx.latch(emitter.add_anonymous_local(ValType::Ref(RefType {
428 nullable: false,
429 heap_type: HeapType::Concrete(intrinsics.raw_string),
430 }))) else {
431 return;
432 };
433 emitter.instruction(Instruction::LocalSet(raw_local));
434 let Some(string_vtable_idx) = ctx.require(
435 ctx.symbols.prelude_global_idx("string_vtable"),
436 "string_vtable imported",
437 ) else {
438 return;
439 };
440 emitter.instruction(Instruction::GlobalGet(string_vtable_idx));
441 emitter.instruction(Instruction::LocalGet(raw_local));
442 emitter.instruction(Instruction::I64Const(0));
443 emitter.instruction(Instruction::StructNew(intrinsics.string));
444}
445
446fn emit_to_json_direct(emitter: &mut FunctionEmitter, ctx: &CodegenCtx, iface: &str) {
447 let mangled = crate::mangle::extend(&crate::mangle::prelude(iface), "toJson");
448 let Some(func_idx) = ctx.latch(ctx.symbols.func_idx(&mangled).ok_or_else(|| {
449 crate::codegen::internal_failure(format!("{iface}#toJson imported from prelude"))
450 })) else {
451 return;
452 };
453 emitter.instruction(Instruction::Call(func_idx));
454}
455
456pub(crate) fn emit_main_output_shim(
461 ctx: &CodegenCtx,
462 main_func_idx: u32,
463 return_ty: &Type,
464 source_return_ty: &Type,
465) -> Result<Function, crate::compiler_error::CompilerFailure> {
466 let mut emitter = FunctionEmitter::new(ctx, &[])?;
467 emitter.instruction(Instruction::Call(main_func_idx));
468 let optional_output = may_stringify_undefined(return_ty);
469 if optional_output {
470 let intr = ctx
471 .symbols
472 .intrinsic_type_indices()
473 .ok_or_else(|| crate::codegen::internal_failure("intrinsics registered"))?;
474 let value = emitter.add_anonymous_local(ValType::Ref(RefType {
475 nullable: true,
476 heap_type: HeapType::Concrete(intr.object),
477 }))?;
478 emitter.instruction(Instruction::LocalSet(value));
479 emitter.instructions(crate::codegen::nullish::is_undefined_or_closure(
480 &[Instruction::LocalGet(value)],
481 intr,
482 ));
483 emitter.emit_if(BlockType::Result(ValType::Ref(RefType {
484 nullable: true,
485 heap_type: HeapType::Concrete(intr.string),
486 })));
487 emitter.instruction(Instruction::RefNull(HeapType::Concrete(intr.string)));
488 emitter.emit_else();
489 emitter.instruction(Instruction::LocalGet(value));
490 }
491 let scalar_output = match source_return_ty.peel() {
492 Type::String
493 | Type::StringLiteral(_)
494 | Type::Number
495 | Type::NumberLiteral(_)
496 | Type::Boolean
497 | Type::BooleanLiteral(_) => true,
498 Type::Union(members) => is_primitive_union(members),
499 _ => false,
500 };
501 if return_ty == &Type::Unknown && scalar_output {
502 emit_nullable_primitive_to_string(&mut emitter, ctx);
503 } else {
504 emit_main_output_value(&mut emitter, ctx, return_ty.peel());
505 }
506 if optional_output {
507 emitter.emit_end();
508 }
509 emitter.build()
510}
511
512fn emit_main_output_value(emitter: &mut FunctionEmitter, ctx: &CodegenCtx, arg_ty: &Type) {
518 match arg_ty {
519 Type::String | Type::StringLiteral(_) => {}
520 Type::Number | Type::NumberLiteral(_) => emit_number_to_string_radix10(emitter, ctx),
521 Type::Boolean | Type::BooleanLiteral(_) => emit_to_string_direct(emitter, ctx, "Boolean"),
522 Type::Union(members) if is_primitive_union(members) => {
526 emit_nullable_primitive_to_string(emitter, ctx);
527 }
528 Type::Undefined
529 | Type::Void
530 | Type::Unknown
531 | Type::TypeVar(_)
532 | Type::GenericParam { .. } => emit_stringify_nullable(emitter, ctx),
533 _ => emit_defined_stringify_value(emitter, ctx, arg_ty),
534 }
535}
536
537fn is_primitive_union(members: &[Type]) -> bool {
541 let mut has_primitive = false;
542 for member in members {
543 match member.peel() {
544 Type::Null | Type::Undefined => {}
545 Type::String
546 | Type::StringLiteral(_)
547 | Type::Number
548 | Type::NumberLiteral(_)
549 | Type::Boolean
550 | Type::BooleanLiteral(_) => has_primitive = true,
551 _ => return false,
552 }
553 }
554 has_primitive
555}
556
557fn emit_nullable_primitive_to_string(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) {
561 let Some(intrinsics) = ctx.require(
562 ctx.symbols.intrinsic_type_indices(),
563 "intrinsics declared by codegen entry",
564 ) else {
565 return;
566 };
567 let Some(obj_tmp) = ctx.latch(emitter.add_anonymous_local(ValType::Ref(RefType {
568 nullable: true,
569 heap_type: HeapType::Concrete(intrinsics.object),
570 }))) else {
571 return;
572 };
573 emitter.instruction(Instruction::LocalTee(obj_tmp));
574 emitter.instruction(Instruction::RefIsNull);
575 emitter.emit_if(BlockType::Result(ValType::Ref(RefType {
576 nullable: false,
577 heap_type: HeapType::Concrete(intrinsics.string),
578 })));
579 ctx.latch(super::emit_inline_string_literal(emitter, ctx, "null"));
580 emitter.emit_else();
581 emitter.instruction(Instruction::LocalGet(obj_tmp));
582 emitter.instruction(Instruction::RefAsNonNull);
583 emit_vtable_dispatch_on_object_stack(emitter, ctx, 0);
584 emitter.emit_end();
585}
586
587fn emit_number_to_string_radix10(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) {
591 emitter.instruction(Instruction::F64Const(10.0_f64.into()));
592 emit_to_string_direct(emitter, ctx, "Number");
593}
594
595fn emit_to_string_direct(emitter: &mut FunctionEmitter, ctx: &CodegenCtx, iface: &str) {
596 let mangled = crate::mangle::extend(&crate::mangle::prelude(iface), "toString");
597 let Some(func_idx) = ctx.latch(ctx.symbols.func_idx(&mangled).ok_or_else(|| {
598 crate::codegen::internal_failure(format!("{iface}#toString imported from prelude"))
599 })) else {
600 return;
601 };
602 emitter.instruction(Instruction::Call(func_idx));
603}
604
605pub(super) fn emit_parse(
606 emitter: &mut FunctionEmitter,
607 ctx: &CodegenCtx,
608 args: &[ExprId],
609) -> Result<(), crate::compiler_error::CompilerFailure> {
610 let [arg] = args else {
611 return Err(crate::codegen::internal_failure(
612 "JSON.parse requires exactly one argument",
613 ));
614 };
615
616 emit_expr(emitter, ctx, *arg)?;
619 super::cast::emit_coerce_to_slot(
620 emitter,
621 ctx,
622 &ctx.ta
623 .try_expr(*arg)
624 .map_err(crate::codegen::arena_failure)?
625 .ty,
626 &Type::String,
627 )?;
628 let string_type_idx = ctx
629 .symbols
630 .string_type_idx()
631 .ok_or_else(|| crate::codegen::internal_failure("$string registered"))?;
632 emitter.instruction(Instruction::StructGet {
633 struct_type_index: string_type_idx,
634 field_index: 1,
635 });
636
637 let parse_idx = ctx
638 .symbols
639 .func_idx(&crate::mangle::host(
640 crate::runtime::JSON_MODULE_NAME,
641 "parse",
642 ))
643 .ok_or_else(|| {
644 crate::codegen::internal_failure("submilli:json.parse imported during codegen")
645 })?;
646 emitter.instruction(Instruction::Call(parse_idx));
649 Ok(())
650}
651
652pub(crate) fn emit_inline_const_raw_string(
655 emitter: &mut FunctionEmitter,
656 ctx: &CodegenCtx,
657 text: &str,
658) -> Result<(), crate::compiler_error::CompilerFailure> {
659 let intrinsics = ctx.symbols.intrinsic_type_indices().ok_or_else(|| {
660 crate::codegen::internal_failure("inline string intrinsic types are missing")
661 })?;
662 let units = crate::literal_units::literal_units(text);
663 let array_size = u32::try_from(units.len()).map_err(|_| {
664 crate::codegen::internal_failure("inline string exceeds the array length representation")
665 })?;
666 for unit in units {
667 emitter.instruction(Instruction::I32Const(i32::from(unit)));
668 }
669 emitter.instruction(Instruction::ArrayNewFixed {
670 array_type_index: intrinsics.raw_string,
671 array_size,
672 });
673 Ok(())
674}
675
676pub(crate) fn emit_raw_string_matches_literal(
677 emitter: &mut FunctionEmitter,
678 intrinsics: IntrinsicTypeIndices,
679 key_raw_local: u32,
680 expected: &str,
681) {
682 let expected = crate::literal_units::literal_units(expected);
683 let Some(expected_length) = emitter.ctx.latch(crate::codegen::wasm_u32(expected.len())) else {
684 return;
685 };
686 let Some(result_local) = emitter.ctx.latch(emitter.add_anonymous_local(ValType::I32)) else {
687 return;
688 };
689 emitter.instruction(Instruction::I32Const(0));
690 emitter.instruction(Instruction::LocalSet(result_local));
691
692 emitter.emit_block(BlockType::Empty);
693
694 emitter.instruction(Instruction::LocalGet(key_raw_local));
695 emitter.instruction(Instruction::ArrayLen);
696 emitter.instruction(Instruction::I32Const(expected_length as i32));
697 emitter.instruction(Instruction::I32Ne);
698 emitter.instruction(Instruction::BrIf(0));
699
700 for (&unit, i) in expected.iter().zip(0..expected_length) {
701 emitter.instruction(Instruction::LocalGet(key_raw_local));
702 emitter.instruction(Instruction::I32Const(i as i32));
703 emitter.instruction(Instruction::ArrayGetU(intrinsics.raw_string));
704 emitter.instruction(Instruction::I32Const(i32::from(unit)));
705 emitter.instruction(Instruction::I32Ne);
706 emitter.instruction(Instruction::BrIf(0));
707 }
708
709 emitter.instruction(Instruction::I32Const(1));
710 emitter.instruction(Instruction::LocalSet(result_local));
711
712 emitter.emit_end();
713
714 emitter.instruction(Instruction::LocalGet(result_local));
715}
716
717#[cfg(test)]
718mod tests {
719 use super::*;
720 use crate::codegen::invariant_tests::{assert_internal, with_context};
721 use crate::codegen::{SymbolTable, tests::mock_symbols_with_intrinsics};
722 use crate::{Span, TypedAst, TypedExpr, TypedExprKind};
723
724 #[test]
725 fn invalid_json_arity_is_an_internal_failure_before_reading_arguments() {
726 with_context(&TypedAst::new(), &SymbolTable::default(), |ctx| {
727 let mut emitter = FunctionEmitter::new(ctx, &[]).unwrap();
728 assert_internal(emit_stringify(&mut emitter, ctx, &[], &Type::String).unwrap_err());
729 assert_internal(emit_parse(&mut emitter, ctx, &[]).unwrap_err());
730 let invalid = crate::ExprId(u32::MAX);
731 assert_internal(
732 emit_stringify(&mut emitter, ctx, &[invalid; 4], &Type::String).unwrap_err(),
733 );
734 assert_internal(emit_parse(&mut emitter, ctx, &[invalid; 2]).unwrap_err());
735 ctx.check_failure().unwrap();
736 });
737 }
738
739 #[test]
740 fn json_emitters_latch_missing_registrations() {
741 type Emit = fn(&mut FunctionEmitter<'_>, &CodegenCtx<'_>);
742 let emitters: &[Emit] = &[
743 emit_stringify_nullable,
744 emit_stringify_string_host,
745 emit_string_on_stack_raw,
746 emit_pretty_number_host,
747 emit_pretty_string_host,
748 emit_wrap_raw_string,
749 emit_nullable_primitive_to_string,
750 |emitter, ctx| emit_to_json_direct(emitter, ctx, "Number"),
751 |emitter, ctx| emit_to_string_direct(emitter, ctx, "Boolean"),
752 |emitter, ctx| {
753 ctx.latch(emit_inline_const_raw_string(emitter, ctx, "\u{1f642}"));
754 },
755 ];
756 for emit in emitters {
757 with_context(&TypedAst::new(), &SymbolTable::default(), |ctx| {
758 emit(&mut FunctionEmitter::new(ctx, &[]).unwrap(), ctx);
759 assert_internal(ctx.check_failure().unwrap_err());
760 });
761 }
762 for emit in [emit_stringify_string_host as Emit, emit_wrap_raw_string] {
763 with_context(&TypedAst::new(), &mock_symbols_with_intrinsics(), |ctx| {
764 emit(&mut FunctionEmitter::new(ctx, &[]).unwrap(), ctx);
765 assert_internal(ctx.check_failure().unwrap_err());
766 });
767 }
768 }
769
770 #[test]
771 fn invalid_space_metadata_returns_internal_failure() {
772 for ty in [Type::Boolean, Type::Error] {
773 let mut ta = TypedAst::new();
774 let space = ta
775 .try_push_expr(TypedExpr {
776 kind: TypedExprKind::Boolean(true),
777 ty,
778 span: Span::at(crate::FileId(0)),
779 })
780 .unwrap();
781 with_context(&ta, &mock_symbols_with_intrinsics(), |ctx| {
782 let mut emitter = FunctionEmitter::new(ctx, &[]).unwrap();
783 match emit_stringify_space_arg(&mut emitter, ctx, space) {
784 Err(error) => assert_internal(error),
785 Ok(_) => panic!("invalid space metadata was accepted"),
786 }
787 });
788 }
789 }
790
791 #[test]
792 fn inline_raw_literals_keep_utf16_units() {
793 with_context(&TypedAst::new(), &mock_symbols_with_intrinsics(), |ctx| {
794 let mut emitter = FunctionEmitter::new(ctx, &[]).unwrap();
795 emit_inline_const_raw_string(&mut emitter, ctx, "a\u{1f642}").unwrap();
796 ctx.check_failure().unwrap();
797 let units: Vec<_> = emitter
798 .instructions
799 .iter()
800 .filter_map(|inst| match inst {
801 Instruction::I32Const(unit) => Some(*unit),
802 _ => None,
803 })
804 .collect();
805 assert_eq!(units, vec![97, 0xd83d, 0xde42]);
806 assert!(matches!(
807 emitter.instructions.last(),
808 Some(Instruction::ArrayNewFixed { array_size: 3, .. })
809 ));
810 });
811 }
812}