@@ 1,5 1,9 @@
//! The actual compiler backend! Should take in a VALIDATED AST and
//! turn it to SPIR-V. Uses the `rspirv` crate for output.
+//!
+//! This produces SPIR-V code suitable for the Vulkan execution model,
+//! see [the Vulkan 1.1 spec, Appendix A](https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/chap40.html#spirvenv-module-validation)
+//! for details, particularly the "Validation Rules within a Module" section.
use fnv::FnvHashMap;
use rspirv::mr::Builder;
@@ 106,7 110,7 @@ impl CContext {
/// Returns the Word bound to the given var name, or panics if not found.
/// All unknown vars should get caught by the validation step.
pub fn lookup_var(&self, name: &str) -> &VarBinding {
- assert!(self.symtable.len() > 1, "No scope for variable lookup!");
+ assert!(self.symtable.len() > 0, "No scope for variable lookup!");
for scope in self.symtable.iter().rev() {
if let Some(w) = scope.get(name) {
return w;
@@ 588,6 592,162 @@ impl CContext {
Ok(())
}
+ /// This compiles an entry point function with no args or return values,
+ /// and takes the args and return values for the given function and turns
+ /// them into input/output variables and does the appropriate loads and stores.
+ ///
+ /// Returns the word of the compiled function, and the words of all global
+ /// vars it touches. It declares its own global vars though. This may not
+ /// always be what we want, but oh well for now.
+ fn compile_entry_stub_function(
+ &mut self,
+ ctx: &verify::VContext,
+ name: &str,
+ ) -> Result<(spirv::Word, Vec<spirv::Word>), crate::Error> {
+ // Dig up the function def, and declare input/output global vars for it.
+ // TODO: Name strings for input/output vars?
+ let def = ctx.get_defined_function(name);
+ if let verify::TypeDef::Function(ref params, ref rettype) = def.functiontype {
+ // Value, value type, pointer type.
+ let input_words: Vec<(spirv::Word, spirv::Word, spirv::Word)> = params
+ .iter()
+ .map(|p| {
+ let t_word = self.get_type(p);
+ // We don't bother storing pointer types in our typetable for now,
+ // this is basically the only place they're used.
+ let t_ptr_word = self
+ .b
+ .type_pointer(None, spirv::StorageClass::Input, t_word);
+ let value_word =
+ self.b
+ .variable(t_ptr_word, None, spirv::StorageClass::Input, None);
+ (value_word, t_word, t_ptr_word)
+ })
+ .collect();
+
+ let (output_word, t_word, _t_ptr_word) = {
+ let t_word = self.get_type(rettype);
+ let t_ptr_word = self
+ .b
+ .type_pointer(None, spirv::StorageClass::Output, t_word);
+ let value_word =
+ self.b
+ .variable(t_ptr_word, None, spirv::StorageClass::Output, None);
+ (value_word, t_word, t_ptr_word)
+ };
+
+ // Now actually compile the function that calls our real function.
+ let functype_word = self.add_type(&verify::TypeDef::Function(
+ vec![],
+ Box::new(verify::TypeDef::Unit),
+ ));
+ let voidtype_word = self.add_type(&verify::TypeDef::Unit);
+
+ let f_word = self.b.begin_function(
+ voidtype_word,
+ None,
+ spirv::FunctionControl::DONT_INLINE | spirv::FunctionControl::CONST,
+ functype_word,
+ )?;
+ self.b.begin_basic_block(None)?;
+ // Load inputs
+ let r_inputs: Result<Vec<spirv::Word>, _> = input_words
+ .clone()
+ .into_iter()
+ .map(|(v_word, t_word, _t_ptr_word)| self.b.load(t_word, None, v_word, None, []))
+ .collect();
+ let inputs = r_inputs?;
+ // Call function
+ let fun_binding = *self.lookup_var(name);
+ let funcall_result_word = self
+ .b
+ .function_call(t_word, None, fun_binding.address, &inputs)
+ .unwrap();
+ // Save output
+ self.b.store(output_word, funcall_result_word, None, [])?;
+ self.b.ret()?;
+ self.b.end_function()?;
+
+ // We must also return the function's interface, that is, the words of
+ // all the global variables it touches.
+ let mut interface: Vec<u32> = input_words
+ .into_iter()
+ .map(|(input_word, _, _)| input_word)
+ .collect();
+ interface.push(output_word);
+
+ // That's it! The actual OpEntryPoint instructions and names and stuff
+ // get handled by the caller.
+ Ok((f_word, interface))
+ } else {
+ unreachable!("Function type is not TypeDef::Function")
+ }
+
+ /*
+ let ftype = self.add_type(&verify::TypeDef::Function(
+ vec![],
+ Box::new(verify::TypeDef::Unit),
+ ));
+ let void = self.add_type(&verify::TypeDef::Unit);
+
+ // Actually make our stub functions
+ let v_word = self.b.begin_function(
+ void,
+ None,
+ spirv::FunctionControl::DONT_INLINE | spirv::FunctionControl::CONST,
+ ftype,
+ )?;
+ self.b.begin_basic_block(None)?;
+ self.b.ret()?;
+ self.b.end_function()?;
+
+ let f_word = self.b.begin_function(
+ void,
+ None,
+ spirv::FunctionControl::DONT_INLINE | spirv::FunctionControl::CONST,
+ ftype,
+ )?;
+ self.b.begin_basic_block(None)?;
+ self.b.ret()?;
+ self.b.end_function()?;
+
+
+ // Compile arguments
+ let param_words: Result<Vec<(spirv::Word, spirv::Word)>, _> = params
+ .clone()
+ .iter()
+ .map(|param| self.compile_expr(param, ctx))
+ .collect();
+ let param_words = param_words?;
+ let param_words: Vec<spirv::Word> = param_words
+ .into_iter()
+ .map(|(value_word, _type_word)| value_word)
+ .collect();
+ // Look up function word
+ // ...which requires having compiled the function first. :|
+ // TODO: Fix! Somehow... If it isn't, this will panic
+ // with variable not found.
+ let binding = *self.lookup_var(&fname);
+ // Get the function's return type. We have to fetch this
+ // from the VContext, since we can't really dig it back
+ // out of the SPIR-V.
+ let functiondef = ctx
+ .functions
+ .get(fname)
+ .expect("Function does not exist for funcall");
+ if let verify::TypeDef::Function(ref _params, ref rettype) = functiondef.functiontype {
+ // TODO: Tail call optimization would go here if it went anywhere.
+ let rettype_word = self.get_type(rettype);
+ let value_word =
+ self.b
+ .function_call(rettype_word, None, binding.address, param_words)?;
+ (value_word, rettype_word)
+ } else {
+ unreachable!("Function type is not TypeDef::Function")
+ }
+ */
+ }
+
/// Okay, so SPIR-V is a bit persnickity about entry points.
/// An entry point MUST be a function that has no parameters
/// or returns, and must describe the things it reads (and writes?)
@@ 603,52 763,78 @@ impl CContext {
/// anything else.
///
/// TODO: Handle it!
- fn mongle_entry_points(&mut self, _ctx: &verify::VContext) -> Result<(), crate::Error> {
- let ftype = self.add_type(&verify::TypeDef::Function(
- vec![],
- Box::new(verify::TypeDef::Unit),
- ));
- let void = self.add_type(&verify::TypeDef::Unit);
-
- // Actually make our stub functions
- let v_word = self.b.begin_function(
- void,
- None,
- spirv::FunctionControl::DONT_INLINE | spirv::FunctionControl::CONST,
- ftype,
- )?;
- self.b.begin_basic_block(None)?;
- self.b.ret()?;
- self.b.end_function()?;
+ fn mongle_entry_points(&mut self, ctx: &verify::VContext) -> Result<(), crate::Error> {
+ for (name, typ) in &[
+ ("vertex", spirv::ExecutionModel::Vertex),
+ ("fragment", spirv::ExecutionModel::Fragment),
+ ][..]
+ {
+ let (e_word, e_interface) = self.compile_entry_stub_function(ctx, name)?;
+ let e_name = format!("_{}_entry", name);
+ self.b.name(e_word, &e_name);
+ self.b.entry_point(*typ, e_word, e_name, e_interface);
- let f_word = self.b.begin_function(
- void,
- None,
- spirv::FunctionControl::DONT_INLINE | spirv::FunctionControl::CONST,
- ftype,
- )?;
- self.b.begin_basic_block(None)?;
- self.b.ret()?;
- self.b.end_function()?;
-
- // We're allowed to reuse the same function as different
- // entry points, so I guess we'll just do that for now
- let vert_name = "_vertex_entry";
- let frag_name = "_fragment_entry";
- self.b.name(v_word, vert_name);
- self.b.name(f_word, frag_name);
- self.b
- .entry_point(spirv::ExecutionModel::Vertex, v_word, vert_name, []);
- self.b
- .entry_point(spirv::ExecutionModel::Fragment, f_word, frag_name, []);
- // TODO: Heck, what the heck to do with this??
- // Well, GLSL uses OriginUpperLeft, so let's do that for now.
- self.b
- .execution_mode(f_word, spirv::ExecutionMode::OriginUpperLeft, []);
+ if let spirv::ExecutionModel::Fragment = typ {
+ // TODO: Heck, what the heck to do with this??
+ // Well, GLSL uses OriginUpperLeft, so let's do that for now.
+ self.b
+ .execution_mode(e_word, spirv::ExecutionMode::OriginUpperLeft, []);
+ }
+ }
Ok(())
/*
+ let frag_name = "_fragment_entry";
+ self.b.name(f_word, frag_name);
+ self.b
+ .entry_point(spirv::ExecutionModel::Vertex, v_word, vert_name, []);
+
+ let ftype = self.add_type(&verify::TypeDef::Function(
+ vec![],
+ Box::new(verify::TypeDef::Unit),
+ ));
+ let void = self.add_type(&verify::TypeDef::Unit);
+
+ // Actually make our stub functions
+ let v_word = self.b.begin_function(
+ void,
+ None,
+ spirv::FunctionControl::DONT_INLINE | spirv::FunctionControl::CONST,
+ ftype,
+ )?;
+ self.b.begin_basic_block(None)?;
+ self.b.ret()?;
+ self.b.end_function()?;
+
+ let f_word = self.b.begin_function(
+ void,
+ None,
+ spirv::FunctionControl::DONT_INLINE | spirv::FunctionControl::CONST,
+ ftype,
+ )?;
+ self.b.begin_basic_block(None)?;
+ self.b.ret()?;
+ self.b.end_function()?;
+
+ // We're allowed to reuse the same function as different
+ // entry points, so I guess we'll just do that for now
+ let vert_name = "_vertex_entry";
+ let frag_name = "_fragment_entry";
+ self.b.name(v_word, vert_name);
+ self.b.name(f_word, frag_name);
+ self.b
+ .entry_point(spirv::ExecutionModel::Vertex, v_word, vert_name, []);
+ self.b
+ .entry_point(spirv::ExecutionModel::Fragment, f_word, frag_name, []);
+ // TODO: Heck, what the heck to do with this??
+ // Well, GLSL uses OriginUpperLeft, so let's do that for now.
+ self.b
+ .execution_mode(f_word, spirv::ExecutionMode::OriginUpperLeft, []);
+
+ Ok(())
+ */
+ /*
// If this function is an entry point we declare it such.
// TODO: This is a little jank but works.
if def.decl.name == "vertex" {
@@ 129,34 129,45 @@ fn test_compile_function() {
let c = compile::compile(&ctx).unwrap();
let m = c.b.module();
+ spirv_val(&m);
let dis = m.disassemble();
let rest = validate_module_header(&dis);
println!("{}", dis);
validate_lines(
rest,
- r#"%6 = OpTypeFunction %1
-%7 = OpTypeFunction %2 %2
-%10 = OpConstant %1 1.0
-%14 = OpTypeFunction %5
-%8 = OpFunction %1 DontInline|Pure|Const %6
-%9 = OpLabel
-OpReturnValue %10
-OpFunctionEnd
-%11 = OpFunction %2 DontInline|Pure|Const %7
-%12 = OpFunctionParameter %2
-%13 = OpLabel
+ r#"%6 = OpTypeFunction %1
+%7 = OpTypeFunction %2 %2
+%10 = OpConstant %1 1.0
+%14 = OpTypePointer Output %1
+%15 = OpVariable %14 Output
+%16 = OpTypeFunction %5
+%20 = OpTypePointer Input %2
+%21 = OpVariable %20 Input
+%22 = OpTypePointer Output %2
+%23 = OpVariable %22 Output
+%8 = OpFunction %1 DontInline|Pure|Const %6
+%9 = OpLabel
+OpReturnValue %10
+OpFunctionEnd
+%11 = OpFunction %2 DontInline|Pure|Const %7
+%12 = OpFunctionParameter %2
+%13 = OpLabel
OpReturnValue %12
OpFunctionEnd
-%15 = OpFunction %5 DontInline|Const %14
-%16 = OpLabel
+%17 = OpFunction %5 DontInline|Const %16
+%18 = OpLabel
+%19 = OpFunctionCall %1 %8
+OpStore %15 %19
OpReturn
OpFunctionEnd
-%17 = OpFunction %5 DontInline|Const %14
-%18 = OpLabel
+%24 = OpFunction %5 DontInline|Const %16
+%25 = OpLabel
+%26 = OpLoad %2 %21
+%27 = OpFunctionCall %2 %11 %26
+OpStore %23 %27
OpReturn
OpFunctionEnd"#,
);
- spirv_val(&m);
}
#[test]
@@ 190,23 201,37 @@ fn test_compile_identity() {
validate_lines(
rest,
r#"%6 = OpTypeFunction %2 %2
-%13 = OpTypeFunction %5
-%7 = OpFunction %1 DontInline|Pure|Const %3
-%8 = OpFunctionParameter %1
+%13 = OpTypePointer Input %1
+%14 = OpVariable %13 Input
+%15 = OpTypePointer Output %1
+%16 = OpVariable %15 Output
+%17 = OpTypeFunction %5
+%22 = OpTypePointer Input %2
+%23 = OpVariable %22 Input
+%24 = OpTypePointer Output %2
+%25 = OpVariable %24 Output
+%7 = OpFunction %1 DontInline|Pure|Const %3
+%8 = OpFunctionParameter %1
%9 = OpLabel
OpReturnValue %8
OpFunctionEnd
-%10 = OpFunction %2 DontInline|Pure|Const %6
-%11 = OpFunctionParameter %2
+%10 = OpFunction %2 DontInline|Pure|Const %6
+%11 = OpFunctionParameter %2
%12 = OpLabel
OpReturnValue %11
OpFunctionEnd
-%14 = OpFunction %5 DontInline|Const %13
-%15 = OpLabel
+%18 = OpFunction %5 DontInline|Const %17
+%19 = OpLabel
+%20 = OpLoad %1 %14
+%21 = OpFunctionCall %1 %7 %20
+OpStore %16 %21
OpReturn
OpFunctionEnd
-%16 = OpFunction %5 DontInline|Const %13
-%17 = OpLabel
+%26 = OpFunction %5 DontInline|Const %17
+%27 = OpLabel
+%28 = OpLoad %2 %23
+%29 = OpFunctionCall %2 %10 %28
+OpStore %25 %29
OpReturn
OpFunctionEnd"#,
);
@@ 246,30 271,41 @@ fn test_compile_literals() {
let rest = validate_module_header(&dis);
validate_lines(
rest,
- r#"%6 = OpTypeFunction %1
+ r#"%6 = OpTypeFunction %1
%7 = OpTypeFunction %2 %2
-%10 = OpConstantTrue %4
+%10 = OpConstantTrue %4
%11 = OpConstantFalse %4
%12 = OpConstant %1 -99.1
-%13 = OpConstant %1 3.0
-%17 = OpTypeFunction %5
-%8 = OpFunction %1 DontInline|Pure|Const %6
+%13 = OpConstant %1 3.0
+%17 = OpTypePointer Output %1
+%18 = OpVariable %17 Output
+%19 = OpTypeFunction %5
+%23 = OpTypePointer Input %2
+%24 = OpVariable %23 Input
+%25 = OpTypePointer Output %2
+%26 = OpVariable %25 Output
+%8 = OpFunction %1 DontInline|Pure|Const %6
%9 = OpLabel
OpReturnValue %13
OpFunctionEnd
-%14 = OpFunction %2 DontInline|Pure|Const %7
-%15 = OpFunctionParameter %2
+%14 = OpFunction %2 DontInline|Pure|Const %7
+%15 = OpFunctionParameter %2
%16 = OpLabel
OpReturnValue %15
OpFunctionEnd
-%18 = OpFunction %5 DontInline|Const %17
-%19 = OpLabel
+%20 = OpFunction %5 DontInline|Const %19
+%21 = OpLabel
+%22 = OpFunctionCall %1 %8
+OpStore %18 %22
OpReturn
OpFunctionEnd
-%20 = OpFunction %5 DontInline|Const %17
-%21 = OpLabel
+%27 = OpFunction %5 DontInline|Const %19
+%28 = OpLabel
+%29 = OpLoad %2 %24
+%30 = OpFunctionCall %2 %14 %29
+OpStore %26 %30
OpReturn
-OpFunctionEnd"#
+OpFunctionEnd"#,
);
spirv_val(&m);
}
@@ 307,25 343,39 @@ fn test_compile_funcall() {
let rest = validate_module_header(&dis);
validate_lines(
rest,
- r#"%13 = OpTypeFunction %5
-%6 = OpFunction %1 DontInline|Pure|Const %3
-%7 = OpFunctionParameter %1
-%8 = OpLabel
+ r#"%13 = OpTypePointer Input %1
+%14 = OpVariable %13 Input
+%15 = OpTypePointer Output %1
+%16 = OpVariable %15 Output
+%17 = OpTypeFunction %5
+%22 = OpTypePointer Input %1
+%23 = OpVariable %22 Input
+%24 = OpTypePointer Output %1
+%25 = OpVariable %24 Output
+%6 = OpFunction %1 DontInline|Pure|Const %3
+%7 = OpFunctionParameter %1
+%8 = OpLabel
OpReturnValue %7
OpFunctionEnd
%9 = OpFunction %1 DontInline|Pure|Const %3
-%10 = OpFunctionParameter %1
-%11 = OpLabel
+%10 = OpFunctionParameter %1
+%11 = OpLabel
%12 = OpFunctionCall %1 %6 %10
OpReturnValue %12
OpFunctionEnd
-%14 = OpFunction %5 DontInline|Const %13
-%15 = OpLabel
+%18 = OpFunction %5 DontInline|Const %17
+%19 = OpLabel
+%20 = OpLoad %1 %14
+%21 = OpFunctionCall %1 %6 %20
+OpStore %16 %21
OpReturn
-OpFunctionEnd
-%16 = OpFunction %5 DontInline|Const %13
-%17 = OpLabel
-OpReturn
+OpFunctionEnd
+%26 = OpFunction %5 DontInline|Const %17
+%27 = OpLabel
+%28 = OpLoad %1 %23
+%29 = OpFunctionCall %1 %9 %28
+OpStore %25 %29
+OpReturn
OpFunctionEnd"#,
);
spirv_val(&m);
@@ 364,25 414,39 @@ fn test_compile_recursive_funcall() {
let rest = validate_module_header(&dis);
validate_lines(
rest,
- r#"%13 = OpTypeFunction %5
-%6 = OpFunction %1 DontInline|Pure|Const %3
-%7 = OpFunctionParameter %1
-%8 = OpLabel
+ r#"%13 = OpTypePointer Input %1
+%14 = OpVariable %13 Input
+%15 = OpTypePointer Output %1
+%16 = OpVariable %15 Output
+%17 = OpTypeFunction %5
+%22 = OpTypePointer Input %1
+%23 = OpVariable %22 Input
+%24 = OpTypePointer Output %1
+%25 = OpVariable %24 Output
+%6 = OpFunction %1 DontInline|Pure|Const %3
+%7 = OpFunctionParameter %1
+%8 = OpLabel
OpReturnValue %7
OpFunctionEnd
%9 = OpFunction %1 DontInline|Pure|Const %3
-%10 = OpFunctionParameter %1
-%11 = OpLabel
+%10 = OpFunctionParameter %1
+%11 = OpLabel
%12 = OpFunctionCall %1 %9 %10
OpReturnValue %12
OpFunctionEnd
-%14 = OpFunction %5 DontInline|Const %13
-%15 = OpLabel
+%18 = OpFunction %5 DontInline|Const %17
+%19 = OpLabel
+%20 = OpLoad %1 %14
+%21 = OpFunctionCall %1 %6 %20
+OpStore %16 %21
OpReturn
-OpFunctionEnd
-%16 = OpFunction %5 DontInline|Const %13
-%17 = OpLabel
-OpReturn
+OpFunctionEnd
+%26 = OpFunction %5 DontInline|Const %17
+%27 = OpLabel
+%28 = OpLoad %1 %23
+%29 = OpFunctionCall %1 %9 %28
+OpStore %25 %29
+OpReturn
OpFunctionEnd"#,
);
spirv_val(&m);
@@ 419,9 483,9 @@ fn test_compile_types_misc() {
rest,
r#"%1 = OpTypeFloat 32
%2 = OpTypeStruct %1 %1 %1 %1
-%3 = OpTypeFunction %1 %1
-%4 = OpTypeBool
-%5 = OpTypeVoid"#
+%3 = OpTypeFunction %1 %1
+%4 = OpTypeBool
+%5 = OpTypeVoid"#,
);
}
@@ 482,10 546,10 @@ fn test_compile_structdefs() {
rest,
r#"%1 = OpTypeFloat 32
%2 = OpTypeStruct %1 %1 %1 %1
-%3 = OpTypeFunction %1 %1
+%3 = OpTypeFunction %1 %1
%4 = OpTypeBool
%5 = OpTypeVoid
-%6 = OpTypeStruct %1 %1 %4
+%6 = OpTypeStruct %1 %1 %4
%7 = OpTypeStruct %6 %1 %6"#,
);
}
@@ 525,7 589,15 @@ fn test_compile_add() {
validate_lines(
rest,
r#"%12 = OpConstant %1 5.0
-%14 = OpTypeFunction %5
+%14 = OpTypePointer Input %1
+%15 = OpVariable %14 Input
+%16 = OpTypePointer Output %1
+%17 = OpVariable %16 Output
+%18 = OpTypeFunction %5
+%23 = OpTypePointer Input %1
+%24 = OpVariable %23 Input
+%25 = OpTypePointer Output %1
+%26 = OpVariable %25 Output
%6 = OpFunction %1 DontInline|Pure|Const %3
%7 = OpFunctionParameter %1
%8 = OpLabel
@@ 537,12 609,18 @@ OpFunctionEnd
%13 = OpFAdd %1 %12 %10
OpReturnValue %13
OpFunctionEnd
-%15 = OpFunction %5 DontInline|Const %14
-%16 = OpLabel
+%19 = OpFunction %5 DontInline|Const %18
+%20 = OpLabel
+%21 = OpLoad %1 %15
+%22 = OpFunctionCall %1 %6 %21
+OpStore %17 %22
OpReturn
OpFunctionEnd
-%17 = OpFunction %5 DontInline|Const %14
-%18 = OpLabel
+%27 = OpFunction %5 DontInline|Const %18
+%28 = OpLabel
+%29 = OpLoad %1 %24
+%30 = OpFunctionCall %1 %9 %29
+OpStore %26 %30
OpReturn
OpFunctionEnd"#,
);
@@ 595,7 673,15 @@ fn test_compile_if() {
r#"%15 = OpConstantTrue %4
%16 = OpConstant %1 5.0
%18 = OpConstant %1 50.0
-%21 = OpTypeFunction %5
+%21 = OpTypePointer Input %1
+%22 = OpVariable %21 Input
+%23 = OpTypePointer Output %1
+%24 = OpVariable %23 Output
+%25 = OpTypeFunction %5
+%30 = OpTypePointer Input %1
+%31 = OpVariable %30 Input
+%32 = OpTypePointer Output %1
+%33 = OpVariable %32 Output
%6 = OpFunction %1 DontInline|Pure|Const %3
%7 = OpFunctionParameter %1
%8 = OpLabel
@@ 616,12 702,18 @@ OpBranch %14
%20 = OpPhi %1 %17 %12 %19 %13
OpReturnValue %20
OpFunctionEnd
-%22 = OpFunction %5 DontInline|Const %21
-%23 = OpLabel
+%26 = OpFunction %5 DontInline|Const %25
+%27 = OpLabel
+%28 = OpLoad %1 %22
+%29 = OpFunctionCall %1 %6 %28
+OpStore %24 %29
OpReturn
OpFunctionEnd
-%24 = OpFunction %5 DontInline|Const %21
-%25 = OpLabel
+%34 = OpFunction %5 DontInline|Const %25
+%35 = OpLabel
+%36 = OpLoad %1 %31
+%37 = OpFunctionCall %1 %9 %36
+OpStore %33 %37
OpReturn
OpFunctionEnd"#,
);