1use crate::error::{FunctionalError, Result};
7use crate::ir::{barrier_segments, AddrSpace, Op, Program, TypeId};
8use crate::memory::{kernarg_load, GlobalArena};
9use crate::sanitize::{SanitizeMode, SanitizeReport, Sanitizer, WorkItemId};
10use serde::{Deserialize, Serialize};
11use std::collections::BTreeMap;
12
13pub const FUNCTIONAL_MODE_MARKER: &str = "softgpu_functional_cpu_not_gfx1201_isa";
15
16pub const MAX_FLAT_WORKITEMS: u64 = 1_048_576;
18pub const MAX_WORKGROUP_FLAT: u32 = 1_024;
19pub const DEFAULT_STEP_BUDGET: u64 = 50_000_000;
20pub const DEFAULT_WAVE_SIZE: u32 = 32;
21pub const MAX_WAVE_SIZE: u32 = 128;
22pub const MAX_GROUP_BYTES: u32 = 64 * 1024;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
25#[serde(rename_all = "snake_case")]
26pub enum SchedulePolicy {
27 LexWorkitem,
29 WaveBarrier,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct LaunchConfig {
35 pub grid: [u32; 3],
36 pub workgroup: [u32; 3],
37}
38
39impl LaunchConfig {
40 pub fn validate(&self) -> Result<()> {
41 for d in 0..3 {
42 if self.workgroup[d] == 0 {
43 return Err(FunctionalError::Validation {
44 detail: format!("workgroup[{d}] must be > 0"),
45 });
46 }
47 if self.grid[d] == 0 {
48 return Err(FunctionalError::Validation {
49 detail: format!("grid[{d}] must be > 0"),
50 });
51 }
52 if self.grid[d] % self.workgroup[d] != 0 {
53 return Err(FunctionalError::Validation {
54 detail: format!(
55 "grid[{d}]={} not divisible by workgroup[{d}]={}",
56 self.grid[d], self.workgroup[d]
57 ),
58 });
59 }
60 }
61 let wg_flat = u64::from(self.workgroup[0])
62 * u64::from(self.workgroup[1])
63 * u64::from(self.workgroup[2]);
64 if wg_flat > u64::from(MAX_WORKGROUP_FLAT) {
65 return Err(FunctionalError::Validation {
66 detail: format!("workgroup flat {wg_flat} > {MAX_WORKGROUP_FLAT}"),
67 });
68 }
69 let flat = u64::from(self.grid[0]) * u64::from(self.grid[1]) * u64::from(self.grid[2]);
70 if flat > MAX_FLAT_WORKITEMS {
71 return Err(FunctionalError::Validation {
72 detail: format!("flat workitems {flat} > {MAX_FLAT_WORKITEMS}"),
73 });
74 }
75 Ok(())
76 }
77
78 pub fn num_workgroups(&self) -> [u32; 3] {
79 [
80 self.grid[0] / self.workgroup[0],
81 self.grid[1] / self.workgroup[1],
82 self.grid[2] / self.workgroup[2],
83 ]
84 }
85
86 pub fn workgroup_flat(&self) -> u32 {
87 self.workgroup[0] * self.workgroup[1] * self.workgroup[2]
88 }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub struct ExecConfig {
93 pub launch: LaunchConfig,
94 pub wave_size: u32,
95 pub group_bytes: u32,
96 pub schedule: SchedulePolicy,
97 pub step_budget: u64,
98 pub sanitize: SanitizeMode,
99}
100
101impl ExecConfig {
102 pub fn from_launch(launch: LaunchConfig) -> Self {
103 Self {
104 launch,
105 wave_size: DEFAULT_WAVE_SIZE,
106 group_bytes: 0,
107 schedule: SchedulePolicy::LexWorkitem,
108 step_budget: DEFAULT_STEP_BUDGET,
109 sanitize: SanitizeMode::Off,
110 }
111 }
112
113 pub fn validate(&self, program: &Program) -> Result<()> {
114 self.launch.validate()?;
115 if self.wave_size == 0 || self.wave_size > MAX_WAVE_SIZE {
116 return Err(FunctionalError::Validation {
117 detail: format!(
118 "wave_size {} out of range 1..={MAX_WAVE_SIZE}",
119 self.wave_size
120 ),
121 });
122 }
123 let need = self.group_bytes.max(program.group_bytes);
124 if need > MAX_GROUP_BYTES {
125 return Err(FunctionalError::Validation {
126 detail: format!("group_bytes {need} > SoftGPU max {MAX_GROUP_BYTES}"),
127 });
128 }
129 if program.has_barrier() && self.schedule != SchedulePolicy::WaveBarrier {
130 return Err(FunctionalError::Validation {
131 detail: "programs with barrier require schedule=wave_barrier".into(),
132 });
133 }
134 Ok(())
135 }
136}
137
138#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
139pub struct RunReport {
140 pub fidelity: &'static str,
141 pub mode: &'static str,
142 pub note: &'static str,
143 pub kernel: String,
144 pub workitems_executed: u64,
145 pub steps: u64,
146 pub grid: [u32; 3],
147 pub workgroup: [u32; 3],
148 pub wave_size: u32,
149 pub schedule: SchedulePolicy,
150 pub barrier_generations: u64,
151 pub atomics_executed: u64,
152}
153
154pub fn run(
156 program: &Program,
157 launch: LaunchConfig,
158 arena: &mut GlobalArena,
159 kernarg: &[u8],
160) -> Result<RunReport> {
161 let mut cfg = ExecConfig::from_launch(launch);
162 cfg.group_bytes = program.group_bytes;
163 if program.has_barrier() {
164 cfg.schedule = SchedulePolicy::WaveBarrier;
165 }
166 Ok(run_with_config_sanitized(program, cfg, arena, kernarg)?.0)
167}
168
169pub fn run_with_budget(
170 program: &Program,
171 launch: LaunchConfig,
172 arena: &mut GlobalArena,
173 kernarg: &[u8],
174 step_budget: u64,
175) -> Result<RunReport> {
176 let mut cfg = ExecConfig::from_launch(launch);
177 cfg.group_bytes = program.group_bytes;
178 cfg.step_budget = step_budget;
179 if program.has_barrier() {
180 cfg.schedule = SchedulePolicy::WaveBarrier;
181 }
182 Ok(run_with_config_sanitized(program, cfg, arena, kernarg)?.0)
183}
184
185pub fn run_with_config(
186 program: &Program,
187 cfg: ExecConfig,
188 arena: &mut GlobalArena,
189 kernarg: &[u8],
190) -> Result<RunReport> {
191 Ok(run_with_config_sanitized(program, cfg, arena, kernarg)?.0)
192}
193
194pub fn run_with_config_sanitized(
196 program: &Program,
197 cfg: ExecConfig,
198 arena: &mut GlobalArena,
199 kernarg: &[u8],
200) -> Result<(RunReport, SanitizeReport)> {
201 let group_len = cfg.group_bytes.max(program.group_bytes) as usize;
202 let sanitizer = Sanitizer::new(cfg.sanitize, arena.len(), group_len)?;
203 run_with_sanitizer(program, cfg, arena, kernarg, sanitizer)
204}
205
206pub fn run_with_sanitizer(
208 program: &Program,
209 cfg: ExecConfig,
210 arena: &mut GlobalArena,
211 kernarg: &[u8],
212 mut sanitizer: Sanitizer,
213) -> Result<(RunReport, SanitizeReport)> {
214 program.validate()?;
215 cfg.validate(program)?;
216
217 let mut steps = 0u64;
218 let mut workitems = 0u64;
219 let mut barrier_generations = 0u64;
220 let mut atomics_executed = 0u64;
221 let nwg = cfg.launch.num_workgroups();
222
223 for gz in 0..nwg[2] {
224 for gy in 0..nwg[1] {
225 for gx in 0..nwg[0] {
226 let group_len = cfg.group_bytes.max(program.group_bytes) as usize;
227 let mut group = GlobalArena::new(group_len);
228 sanitizer.reset_group();
229 sanitizer.barrier_gen = 0;
230 let (s, wi, bg, at) = match cfg.schedule {
231 SchedulePolicy::LexWorkitem => run_workgroup_lex(
232 program,
233 &cfg,
234 arena,
235 &mut group,
236 kernarg,
237 [gx, gy, gz],
238 cfg.step_budget.saturating_sub(steps),
239 &mut sanitizer,
240 )?,
241 SchedulePolicy::WaveBarrier => run_workgroup_wave_barrier(
242 program,
243 &cfg,
244 arena,
245 &mut group,
246 kernarg,
247 [gx, gy, gz],
248 cfg.step_budget.saturating_sub(steps),
249 &mut sanitizer,
250 )?,
251 };
252 steps = steps.saturating_add(s);
253 if steps > cfg.step_budget {
254 return Err(FunctionalError::StepBudgetExceeded { steps });
255 }
256 workitems += wi;
257 barrier_generations = barrier_generations.saturating_add(bg);
258 atomics_executed = atomics_executed.saturating_add(at);
259 }
260 }
261 }
262
263 let report = RunReport {
264 fidelity: "functional",
265 mode: FUNCTIONAL_MODE_MARKER,
266 note: "not_gfx1201_isa_emulation",
267 kernel: program.name.clone(),
268 workitems_executed: workitems,
269 steps,
270 grid: cfg.launch.grid,
271 workgroup: cfg.launch.workgroup,
272 wave_size: cfg.wave_size,
273 schedule: cfg.schedule,
274 barrier_generations,
275 atomics_executed,
276 };
277 Ok((report, sanitizer.into_report()))
278}
279
280#[allow(clippy::too_many_arguments)]
281fn run_workgroup_lex(
282 program: &Program,
283 cfg: &ExecConfig,
284 global: &mut GlobalArena,
285 group: &mut GlobalArena,
286 kernarg: &[u8],
287 wg: [u32; 3],
288 budget: u64,
289 sanitizer: &mut Sanitizer,
290) -> Result<(u64, u64, u64, u64)> {
291 let mut steps = 0u64;
292 let mut atomics = 0u64;
293 let mut workitems = 0u64;
294 let launch = cfg.launch;
295 for lz in 0..launch.workgroup[2] {
296 for ly in 0..launch.workgroup[1] {
297 for lx in 0..launch.workgroup[0] {
298 let local = [lx, ly, lz];
299 let flat = flat_local(local, launch.workgroup);
300 let global_id = [
301 wg[0] * launch.workgroup[0] + lx,
302 wg[1] * launch.workgroup[1] + ly,
303 wg[2] * launch.workgroup[2] + lz,
304 ];
305 let mut lane = Lane::new(
306 global_id,
307 local,
308 wg,
309 flat / cfg.wave_size,
310 flat % cfg.wave_size,
311 cfg.wave_size,
312 );
313 let (used, at) = exec_ops(
314 &program.body,
315 std::slice::from_mut(&mut lane),
316 &[true],
317 global,
318 group,
319 kernarg,
320 budget.saturating_sub(steps),
321 true,
322 launch.workgroup,
323 sanitizer,
324 )?;
325 steps = steps.saturating_add(used);
326 atomics = atomics.saturating_add(at);
327 if steps > budget {
328 return Err(FunctionalError::StepBudgetExceeded { steps });
329 }
330 workitems += 1;
331 }
332 }
333 }
334 Ok((steps, workitems, 0, atomics))
335}
336
337#[allow(clippy::too_many_arguments)]
338fn run_workgroup_wave_barrier(
339 program: &Program,
340 cfg: &ExecConfig,
341 global: &mut GlobalArena,
342 group: &mut GlobalArena,
343 kernarg: &[u8],
344 wg: [u32; 3],
345 budget: u64,
346 sanitizer: &mut Sanitizer,
347) -> Result<(u64, u64, u64, u64)> {
348 let launch = cfg.launch;
349 let flat_n = launch.workgroup_flat();
350 let mut lanes: Vec<Lane> = Vec::with_capacity(flat_n as usize);
351 for flat in 0..flat_n {
352 let local = unflat_local(flat, launch.workgroup);
353 let global_id = [
354 wg[0] * launch.workgroup[0] + local[0],
355 wg[1] * launch.workgroup[1] + local[1],
356 wg[2] * launch.workgroup[2] + local[2],
357 ];
358 lanes.push(Lane::new(
359 global_id,
360 local,
361 wg,
362 flat / cfg.wave_size,
363 flat % cfg.wave_size,
364 cfg.wave_size,
365 ));
366 }
367
368 let segments = barrier_segments(&program.body)?;
369 let barrier_gens = segments.len().saturating_sub(1) as u64;
370 let mut steps = 0u64;
371 let mut atomics = 0u64;
372 let n_waves = flat_n.div_ceil(cfg.wave_size);
373
374 for (si, seg) in segments.iter().enumerate() {
375 for wave in 0..n_waves {
376 let lo = (wave * cfg.wave_size) as usize;
377 let hi = ((wave * cfg.wave_size + cfg.wave_size).min(flat_n)) as usize;
378 let mask: Vec<bool> = (lo..hi).map(|_| true).collect();
379 let (used, at) = exec_ops(
380 seg,
381 &mut lanes[lo..hi],
382 &mask,
383 global,
384 group,
385 kernarg,
386 budget.saturating_sub(steps),
387 false,
388 launch.workgroup,
389 sanitizer,
390 )?;
391 steps = steps.saturating_add(used);
392 atomics = atomics.saturating_add(at);
393 if steps > budget {
394 return Err(FunctionalError::StepBudgetExceeded { steps });
395 }
396 }
397 if si + 1 < segments.len() {
398 sanitizer.note_barrier();
399 }
400 }
401
402 Ok((steps, u64::from(flat_n), barrier_gens, atomics))
403}
404
405fn flat_local(local: [u32; 3], wg: [u32; 3]) -> u32 {
406 local[0] + local[1] * wg[0] + local[2] * wg[0] * wg[1]
407}
408
409fn unflat_local(flat: u32, wg: [u32; 3]) -> [u32; 3] {
410 let x = flat % wg[0];
411 let t = flat / wg[0];
412 let y = t % wg[1];
413 let z = t / wg[1];
414 [x, y, z]
415}
416
417#[derive(Debug, Clone)]
418struct Lane {
419 global: [u32; 3],
420 local: [u32; 3],
421 wg: [u32; 3],
422 wave_id: u32,
423 lane_id: u32,
424 wave_size: u32,
425 regs: BTreeMap<String, i64>,
426}
427
428impl Lane {
429 fn new(
430 global: [u32; 3],
431 local: [u32; 3],
432 wg: [u32; 3],
433 wave_id: u32,
434 lane_id: u32,
435 wave_size: u32,
436 ) -> Self {
437 Self {
438 global,
439 local,
440 wg,
441 wave_id,
442 lane_id,
443 wave_size,
444 regs: BTreeMap::new(),
445 }
446 }
447}
448
449#[allow(clippy::too_many_arguments)]
450fn exec_ops(
451 ops: &[Op],
452 lanes: &mut [Lane],
453 mask: &[bool],
454 global: &mut GlobalArena,
455 group: &mut GlobalArena,
456 kernarg: &[u8],
457 budget: u64,
458 stop_at_ret: bool,
459 workgroup: [u32; 3],
460 sanitizer: &mut Sanitizer,
461) -> Result<(u64, u64)> {
462 let mut steps = 0u64;
463 let mut atomics = 0u64;
464
465 for op in ops {
466 if matches!(op, Op::Ret) {
467 if stop_at_ret {
468 return Ok((steps, atomics));
469 }
470 continue;
471 }
472 if matches!(op, Op::Barrier) {
473 return Err(FunctionalError::Internal(
474 "barrier must be handled by segment splitter".into(),
475 ));
476 }
477
478 match op {
479 Op::If {
480 cond,
481 then_body,
482 else_body,
483 } => {
484 let mut then_mask = vec![false; lanes.len()];
485 let mut else_mask = vec![false; lanes.len()];
486 for (i, lane) in lanes.iter().enumerate() {
487 if !mask[i] {
488 continue;
489 }
490 let c = get_reg(&lane.regs, cond)?;
491 if c != 0 {
492 then_mask[i] = true;
493 } else {
494 else_mask[i] = true;
495 }
496 }
497 let (s1, a1) = exec_ops(
498 then_body,
499 lanes,
500 &then_mask,
501 global,
502 group,
503 kernarg,
504 budget.saturating_sub(steps),
505 false,
506 workgroup,
507 sanitizer,
508 )?;
509 steps = steps.saturating_add(s1);
510 atomics = atomics.saturating_add(a1);
511 let (s2, a2) = exec_ops(
512 else_body,
513 lanes,
514 &else_mask,
515 global,
516 group,
517 kernarg,
518 budget.saturating_sub(steps),
519 false,
520 workgroup,
521 sanitizer,
522 )?;
523 steps = steps.saturating_add(s2);
524 atomics = atomics.saturating_add(a2);
525 }
526 Op::While { cond, body } => loop {
527 let mut iter_mask = vec![false; lanes.len()];
528 let mut any = false;
529 for (i, lane) in lanes.iter().enumerate() {
530 if !mask[i] {
531 continue;
532 }
533 let c = get_reg(&lane.regs, cond)?;
534 if c != 0 {
535 iter_mask[i] = true;
536 any = true;
537 }
538 }
539 if !any {
540 break;
541 }
542 let (s, a) = exec_ops(
543 body,
544 lanes,
545 &iter_mask,
546 global,
547 group,
548 kernarg,
549 budget.saturating_sub(steps),
550 false,
551 workgroup,
552 sanitizer,
553 )?;
554 steps = steps.saturating_add(s);
555 atomics = atomics.saturating_add(a);
556 if steps > budget {
557 return Err(FunctionalError::StepBudgetExceeded { steps });
558 }
559 },
560 other => {
561 for (i, lane) in lanes.iter_mut().enumerate() {
562 if !mask[i] {
563 continue;
564 }
565 steps += 1;
566 if steps > budget {
567 return Err(FunctionalError::StepBudgetExceeded { steps });
568 }
569 sanitizer.step = sanitizer.step.saturating_add(1);
570 let at =
571 exec_lane_op(other, lane, global, group, kernarg, workgroup, sanitizer)?;
572 atomics = atomics.saturating_add(at);
573 }
574 }
575 }
576 if steps > budget {
577 return Err(FunctionalError::StepBudgetExceeded { steps });
578 }
579 }
580 Ok((steps, atomics))
581}
582
583fn actor_of(lane: &Lane, workgroup: [u32; 3]) -> WorkItemId {
584 WorkItemId {
585 workgroup: lane.wg,
586 wave: lane.wave_id,
587 lane: lane.lane_id,
588 flat_local: flat_local(lane.local, workgroup),
589 }
590}
591
592#[allow(clippy::too_many_arguments)]
593fn exec_lane_op(
594 op: &Op,
595 lane: &mut Lane,
596 global: &mut GlobalArena,
597 group: &mut GlobalArena,
598 kernarg: &[u8],
599 workgroup: [u32; 3],
600 sanitizer: &mut Sanitizer,
601) -> Result<u64> {
602 let actor = actor_of(lane, workgroup);
603 match op {
604 Op::Const { dst, ty, value } => {
605 lane.regs.insert(dst.clone(), narrow(*value, *ty)?);
606 Ok(0)
607 }
608 Op::GlobalId { dst, dim } => {
609 lane.regs
610 .insert(dst.clone(), i64::from(lane.global[*dim as usize]));
611 Ok(0)
612 }
613 Op::LocalId { dst, dim } => {
614 lane.regs
615 .insert(dst.clone(), i64::from(lane.local[*dim as usize]));
616 Ok(0)
617 }
618 Op::WorkgroupId { dst, dim } => {
619 lane.regs
620 .insert(dst.clone(), i64::from(lane.wg[*dim as usize]));
621 Ok(0)
622 }
623 Op::LaneId { dst } => {
624 lane.regs.insert(dst.clone(), i64::from(lane.lane_id));
625 Ok(0)
626 }
627 Op::WaveId { dst } => {
628 lane.regs.insert(dst.clone(), i64::from(lane.wave_id));
629 Ok(0)
630 }
631 Op::WaveSize { dst } => {
632 lane.regs.insert(dst.clone(), i64::from(lane.wave_size));
633 Ok(0)
634 }
635 Op::Add { dst, lhs, rhs, ty } => {
636 let a = get_reg(&lane.regs, lhs)?;
637 let b = get_reg(&lane.regs, rhs)?;
638 lane.regs
639 .insert(dst.clone(), narrow(a.wrapping_add(b), *ty)?);
640 Ok(0)
641 }
642 Op::Sub { dst, lhs, rhs, ty } => {
643 let a = get_reg(&lane.regs, lhs)?;
644 let b = get_reg(&lane.regs, rhs)?;
645 lane.regs
646 .insert(dst.clone(), narrow(a.wrapping_sub(b), *ty)?);
647 Ok(0)
648 }
649 Op::Mul { dst, lhs, rhs, ty } => {
650 let a = get_reg(&lane.regs, lhs)?;
651 let b = get_reg(&lane.regs, rhs)?;
652 lane.regs
653 .insert(dst.clone(), narrow(a.wrapping_mul(b), *ty)?);
654 Ok(0)
655 }
656 Op::CmpEq { dst, lhs, rhs, ty } => {
657 let a = narrow(get_reg(&lane.regs, lhs)?, *ty)?;
658 let b = narrow(get_reg(&lane.regs, rhs)?, *ty)?;
659 lane.regs.insert(dst.clone(), i64::from(a == b));
660 Ok(0)
661 }
662 Op::CmpNe { dst, lhs, rhs, ty } => {
663 let a = narrow(get_reg(&lane.regs, lhs)?, *ty)?;
664 let b = narrow(get_reg(&lane.regs, rhs)?, *ty)?;
665 lane.regs.insert(dst.clone(), i64::from(a != b));
666 Ok(0)
667 }
668 Op::And { dst, lhs, rhs, ty } => {
669 let a = get_reg(&lane.regs, lhs)?;
670 let b = get_reg(&lane.regs, rhs)?;
671 let v = match ty {
672 TypeId::I32 => i64::from((a as i32) & (b as i32)),
673 TypeId::U32 => i64::from((a as u32) & (b as u32)),
674 TypeId::U64 => (a as u64 & b as u64) as i64,
675 };
676 lane.regs.insert(dst.clone(), v);
677 Ok(0)
678 }
679 Op::KernargLoad { dst, offset, ty } => {
680 lane.regs
681 .insert(dst.clone(), kernarg_load(kernarg, *offset, *ty)?);
682 Ok(0)
683 }
684 Op::LoadGlobal { dst, addr, ty } => {
685 let a = get_reg(&lane.regs, addr)? as u64;
686 sanitizer.on_access(AddrSpace::Global, a, *ty, false, false, actor)?;
687 lane.regs.insert(dst.clone(), global.load(a, *ty)?);
688 Ok(0)
689 }
690 Op::StoreGlobal { addr, src, ty } => {
691 let a = get_reg(&lane.regs, addr)? as u64;
692 let v = get_reg(&lane.regs, src)?;
693 sanitizer.on_access(AddrSpace::Global, a, *ty, true, false, actor)?;
694 global.store(a, *ty, v)?;
695 Ok(0)
696 }
697 Op::LoadGroup { dst, addr, ty } => {
698 let a = get_reg(&lane.regs, addr)? as u64;
699 sanitizer.on_access(AddrSpace::Group, a, *ty, false, false, actor)?;
700 lane.regs.insert(dst.clone(), group.load(a, *ty)?);
701 Ok(0)
702 }
703 Op::StoreGroup { addr, src, ty } => {
704 let a = get_reg(&lane.regs, addr)? as u64;
705 let v = get_reg(&lane.regs, src)?;
706 sanitizer.on_access(AddrSpace::Group, a, *ty, true, false, actor)?;
707 group.store(a, *ty, v)?;
708 Ok(0)
709 }
710 Op::AtomicAdd {
711 dst,
712 addr,
713 src,
714 space,
715 scope: _,
716 order: _,
717 } => {
718 let a = get_reg(&lane.regs, addr)? as u64;
719 let v = get_reg(&lane.regs, src)?;
720 sanitizer.on_access(*space, a, TypeId::I32, true, true, actor)?;
721 let arena = match space {
722 AddrSpace::Global => &mut *global,
723 AddrSpace::Group => &mut *group,
724 };
725 let old = arena.load(a, TypeId::I32)?;
726 let newv = i64::from((old as i32).wrapping_add(v as i32));
727 arena.store(a, TypeId::I32, newv)?;
728 lane.regs.insert(dst.clone(), old);
729 Ok(1)
730 }
731 Op::Barrier | Op::If { .. } | Op::While { .. } | Op::Ret => Err(FunctionalError::Internal(
732 format!("unexpected op in exec_lane_op: {op:?}"),
733 )),
734 }
735}
736
737fn get_reg(regs: &BTreeMap<String, i64>, name: &str) -> Result<i64> {
738 regs.get(name)
739 .copied()
740 .ok_or_else(|| FunctionalError::UndefinedReg {
741 name: name.to_string(),
742 })
743}
744
745fn narrow(v: i64, ty: TypeId) -> Result<i64> {
746 Ok(match ty {
747 TypeId::I32 => v as i32 as i64,
748 TypeId::U32 => (v as u32) as i64,
749 TypeId::U64 => v,
750 })
751}