To protect your data, the CISO officer has suggested users to enable GitLab 2FA as soon as possible.

Commit c73afe29 authored by John Zhang's avatar John Zhang
Browse files

wip: refactored Mu language module so that it uses the callback written in C

parent 69da0ca3
......@@ -20,7 +20,7 @@ __VERSION__ = "0.0.1"
if resource_isdir(__name__, "suite"):
SUITE_DIR = Path(resource_filename(__name__, "suite"))
CALLBACKS_DIR = Path(resource_filename(__name__, "callbacks"))
else:
SUITE_DIR = Path(__file__) / 'suite'
CALLBACKS_DIR = SUITE_DIR / 'callbacks'
CALLBACKS_DIR = Path(__file__) / 'callbacks'
......@@ -17,9 +17,8 @@ limitations under the License.
This header file defines callback interface for C.
*/
struct Callback; // allow for implementation defined struct
struct Callback* cb_init(const char* param_s);
void cb_begin(struct Callback* cb); // data is implementation defined
void cb_end(struct Callback* cb);
void cb_report(struct Callback* cb);
void* cb_init(const char* param_s); // use void* to hide implementation details
void cb_begin(void* cb); // data is implementation defined
void cb_end(void* cb);
void cb_report(void* cb);
......@@ -64,25 +64,28 @@ typedef struct Callback Callback;
/**
* param_s: accept an at most two digit integer string for float format precision
*/
struct Callback *cb_init(const char *param_s) {
void* cb_init(const char *param_s) {
Callback *cb;
cb = (Callback *)malloc(sizeof(Callback));
cb->fltfmtprec = strlen(param_s) > 0 ? atoi(param_s) : DEFAULT_FLOAT_FORMAT_PREC;
return cb;
return (void*)cb;
}
// NOTE: assuming current strategy of only one data point per run.
void cb_begin(struct Callback *cb) {
void cb_begin(void *p) {
Callback *cb = (Callback*)p;
// call time measurement function
get_nano_timestamp(&cb->t0);
}
void cb_end(struct Callback *cb) {
void cb_end(void *p) {
Callback *cb = (Callback*)p;
get_nano_timestamp(&cb->t1);
}
void cb_report(struct Callback *cb) {
void cb_report(void *p) {
char fmtstr [6]; // NOTE: assume precision number is at most two digit
Callback *cb = (Callback*)p;
sprintf(fmtstr, "%%.%dlf", cb->fltfmtprec);
printf(fmtstr, get_elapsed_time(&cb->t0, &cb->t1));
......
......@@ -19,21 +19,19 @@ limitations under the License.
#include "callback.h"
struct Callback {}; // nothing
struct Callback* cb_init(const char* param_s) {
void* cb_init(const char* param_s) {
printf("initialised PrintCallback with: %s\n", param_s);
return malloc(sizeof(struct Callback));
return NULL;
}
void cb_begin(struct Callback* cb) {
void cb_begin(void* cb) {
printf("begin measurement.\n");
}
void cb_end(struct Callback* cb) {
void cb_end(void* cb) {
printf("end measurement.\n");
}
void cb_report(struct Callback* cb) {
void cb_report(void* cb) {
print("report.\n");
}
......@@ -20,7 +20,7 @@ limitations under the License.
void mubench_init_callback_symbols(MuIRBuilder *bldr)
{
// generate symbols
t_callback = bldr->gen_sym(bldr, "@mubench.t_callback");
t_void = bldr->gen_sym(bldr, "@mubench.t_void");
t_callback_p = bldr->gen_sym(bldr, "@mubench.t_callback_p");
sig_cb_init = bldr->gen_sym(bldr, "@mubench.sig_cb_init");
sig_cb_begin = bldr->gen_sym(bldr, "@mubench.sig_cb_begin");
......@@ -39,7 +39,8 @@ void mubench_init_callback_symbols(MuIRBuilder *bldr)
t_ccharp = bldr->gen_sym(bldr, "@mubench.t_ccharp");
// define types
bldr->new_type_uptr(bldr, t_callback_p, t_callback);
bldr->new_type_void(bldr, t_void);
bldr->new_type_uptr(bldr, t_callback_p, t_void);
bldr->new_funcsig(bldr, sig_cb_init,
(MuTypeNode[1]){t_ccharp}, 1,
(MuTypeNode[1]){t_callback_p}, 1);
......
......@@ -31,7 +31,8 @@ extern "C" {
/**
The interface consists of the following definitions:
.type @mubench.t_callback_p = uptr<@mubench.t_callback>
.type @mubench.t_void = void
.type @mubench.t_callback_p = uptr<@mubench.t_void>
.type @mubench.t_cchar = int<8>
.type @mubench.t_cchara = hybrid<@mubench.t_cchar>
.type @mubench.t_ccharp = uptr<@mubench.t_cchara>
......@@ -47,12 +48,9 @@ The interface consists of the following definitions:
.const @mubench.extern_cb_begin <@mubench.t_fp_cb_begin> = EXTERN "cb_begin"
.const @mubench.extern_cb_end <@mubench.t_fp_cb_end> = EXTERN "cb_end"
.const @mubench.extern_cb_report <@mubench.t_fp_cb_report> = EXTERN "cb_report"
relying on subsequent functions to define:
.type @mubench.t_callback
*/
MuID t_callback;
MuID t_void;
MuID t_callback_p;
MuID sig_cb_init;
MuID sig_cb_begin;
......@@ -76,25 +74,6 @@ MuID t_ccharp;
*/
void mubench_init_callback_symbols(MuIRBuilder* bldr);
/**
* Implementation defined function.
* This function is called before calling build_benchmark.
* Build callback functions, concrete types and other global definitions.
* Must define the following types:
@mubench.callback
@mubench.args
*/
void mubench_build_callback(MuIRBuilder* bldr);
/**
* Implementation defined function.
* Allocate memory to parr,
* fill it with global definitions that should be included in the boot image.
* Return the length of the array.
* NOTE: array memory should be freed by the caller when it's no long needed.
*/
int mubench_get_callback_global_defs(MuID* *parr);
#ifdef __cplusplus
}
#endif
......
......@@ -85,7 +85,6 @@ int main(int argc, char **argv)
bldr = ctx->new_ir_builder(ctx);
mubench_init_callback_symbols(bldr);
mubench_build_callback(bldr);
prim_func_id = mubench_build_benchmark(bldr);
bldr->load(bldr);
hmain = ctx->handle_from_func(ctx, prim_func_id);
......@@ -93,7 +92,7 @@ int main(int argc, char **argv)
// get the global definitions and merge them into one array
MuID predef_ids[] = {
t_callback,
t_void,
t_callback_p,
sig_cb_init,
sig_cb_begin,
......@@ -113,18 +112,16 @@ int main(int argc, char **argv)
};
int npredefs = sizeof(predef_ids) / sizeof(MuID);
ngdefs_cb = mubench_get_callback_global_defs(&gdefs_cb);
ngdefs_bench = mubench_get_benchmark_global_defs(&gdefs_bench);
gdefs = (MuID *)malloc((npredefs + ngdefs_cb + ngdefs_bench) * sizeof(MuID));
gdefs = (MuID *)malloc((npredefs + ngdefs_bench) * sizeof(MuID));
memcpy(gdefs, predef_ids, sizeof(predef_ids));
memcpy(gdefs + npredefs, gdefs_cb, ngdefs_cb * sizeof(MuID));
memcpy(gdefs + npredefs + ngdefs_cb, gdefs_bench, ngdefs_bench * sizeof(MuID));
memcpy(gdefs + npredefs, gdefs_bench, ngdefs_bench * sizeof(MuID));
// get relocation info
mubench_get_reloc_info(&sym_flds, &sym_strs, &nsyms, &reloc_flds, &reloc_strs, &nrelocs);
ctx->make_boot_image(ctx, gdefs, (npredefs + ngdefs_cb + ngdefs_bench),
ctx->make_boot_image(ctx, gdefs, (npredefs + ngdefs_bench),
hmain, NULL, NULL,
sym_flds, sym_strs, nsyms, reloc_flds, reloc_strs, nrelocs,
argv[2]);
......@@ -135,7 +132,6 @@ int main(int argc, char **argv)
// free memory
free(gdefs);
free(gdefs_cb);
free(gdefs_bench);
if (sym_flds)
{
......
......@@ -12,10 +12,14 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import time
import logging
import subprocess as subproc
from pathlib import Path
from mubench import CALLBACKS_DIR
from mubench.util import expandenv
from mubench.exceptions import ExecutionFailure
logger = logging.getLogger(__name__)
......@@ -25,6 +29,7 @@ class Language:
name = None
src_ext = None
compiled = True
default_cc = 'clang'
@classmethod
def check_lang(cls, lc):
......@@ -54,7 +59,7 @@ class Language:
raise NotImplementedError
@classmethod
def run_in_subproc(cls, cmd, env=None):
def run_in_subproc(cls, cmd, env=None): # TODO: maybe put this in util.py
cmd = list(map(str, cmd))
logger.info(" ".join(cmd))
t0 = time.perf_counter()
......
......@@ -77,22 +77,26 @@ class Mu(Language):
def set_default_args(defl_d, vmargs):
# include default args if not specified, otherwise use custom-defined value
for key, val in defl_d.items():
if not any([key in arg for arg in vmargs]):
if not key in vmargs:
vmargs.append('%(key)s=%(val)s' % locals())
return vmargs
vmargs = list(map(lambda a: expandenv(a, task.env), cc.get('vmargs', [])))
libcb = task.taskset.callback['dylib']
if lc['impl'] == 'holstein':
separators = '\n'
default_args = {}
default_args = {
'extraLibs': str(libcb)
}
else: # zebu
separators = ' '
emit_dir = task.output_dir / ('%s_%s-emit' % (task.taskset.name, task.name))
default_args = {
'--aot-emit-dir': str(emit_dir)
'--aot-emit-dir': str(emit_dir),
'--bootimage-external-libpath': str(libcb.parent),
'--bootimage-external-lib': "cb_%(name)s" % task.callback # libcb_%(name)s.so/dylib
}
vmargs = set_default_args(default_args, vmargs)
vmarg_s = separators.join(vmargs)
vmarg_s = separators.join(set_default_args(default_args, vmargs))
if lc['impl'] == 'zebu':
vmarg_s = 'init_mu ' + vmarg_s
cc['vmarg_s'] = vmarg_s
......@@ -107,7 +111,9 @@ class Mu(Language):
rc.setdefault('exec', default_exec)
rc['exec'] = Path(expandenv(str(rc['exec']), env))
holstein_default_flags = ['--uPtrHack=True']
holstein_default_flags = [
'--uPtrHack=True',
'--extraLibs=%s' % task.taskset.callback['dylib']]
rc['flags'] = holstein_default_flags + rc.get('flags', [])
return rc
......@@ -142,7 +148,6 @@ class Mu(Language):
srcs = [
callback_dir / 'build_callbacks.c',
callback_dir / 'main.c',
callback_dir / ('build_cb_%(name)s.c' % task.callback),
bm_src
]
cmd.extend(srcs)
......
......@@ -14,6 +14,7 @@
# limitations under the License.
import os
import sys
import yaml
import logging
import crayons
......@@ -22,11 +23,11 @@ import json
from pathlib import Path
from types import SimpleNamespace
from mubench import SUITE_DIR
from mubench import SUITE_DIR, CALLBACKS_DIR
from mubench.exceptions import ExecutionFailure
from mubench.conf import settings
from mubench.util import expandenv, dictify
from mubench.lang import get_lang
from mubench.lang import get_lang, Language
from mubench.models.result import Result
logger = logging.getLogger(__name__)
......@@ -66,6 +67,13 @@ class TaskSet:
map(lambda a: expandenv(str(a), self.env), benchmark['args']))
callback['param'] = expandenv(callback['param'], self.env)
# compiled callback shared library
self.callback['dylib'] = self.output_dir / ('libcb_%(name)s.dylib' % self.callback)
if sys.platform == 'darwin':
self.env['DYLD_LIBRARY_PATH'] = str(self.output_dir)
else:
self.env['LD_LIBRARY_PATH'] = str(self.output_dir)
@staticmethod
def from_config_dict(name, conf_d, conf_dir=None):
# output directory
......@@ -93,6 +101,12 @@ class TaskSet:
d = dictify(conf_d['callback'])
if 'param' not in d or d['param'] is None:
d['param'] = "" # default to ""
if 'include_dirs' not in d or d['include_dirs'] is None:
d['include_dirs'] = [] # default to []
if 'library_dirs' not in d or d['library_dirs'] is None:
d['library_dirs'] = [] # default to []
if 'libraries' not in d or d['libraries'] is None:
d['libraries'] = [] # default to []
conf_d['callback'] = d
# add comparison
......@@ -120,6 +134,9 @@ class TaskSet:
return ts
def run(self, skipcomp_l):
# compile callback into shared library first
self.compile_callback()
# compile first
targets = {}
for task in self.tasks:
......@@ -188,6 +205,38 @@ class TaskSet:
return self.results
# TODO: debug
def compile_callback(self):
cmd = []
cc = self.env.get('CC', 'clang')
cmd.append(cc)
cmd.append('--shared')
# include_dirs
for d in self.callback['include_dirs']:
p = Path(expandenv(d, self.env))
if not p.is_absolute():
p = CALLBACKS_DIR / p # default relative to CALLBACKS_DIR
cmd.append('-I%s' % p)
# library_dirs
for d in self.callback['library_dirs']:
p = Path(expandenv(d, self.env))
if not p.is_absolute():
p = CALLBACKS_DIR / p # default relative to CALLBACKS_DIR
cmd.append('-L%s' % p)
# libraries
for lib in self.callback['libraries']:
cmd.append('-l %s' % lib)
# output
cmd.extend(['-o', self.callback['dylib']])
# source
cmd.append(CALLBACKS_DIR / ('cb_%(name)s.c' % self.callback))
Language.run_in_subproc(cmd, self.env)
class Task:
"""
......
/*
Copyright 2017 The Australian National University
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* Build time measurement callback using clock_gettime function.
.type @mubench.t_callback = struct<@clockcb.t_int @clockcb.t_timestamp_t @clockcb.t_timestamp_t>
.type @clockcb.t_int = int<sizeof(int) * 8>
.type @clockcb.t_i64 = int<64>
.type @clockcb.t_long = int<sizeof(long) * 8>
.type @clockcb.t_time_t = int<sizeof(time_t) * 8>
.type @clockcb.t_timespec = struct <@clockcb.t_time_t @clockcb.t_long>
.type @clockcb.t_timespec_p = uptr<@clockcb.t_timespec>
.type @clockcb.t_timestamp_t = @clockcb.t_timespec or @clockcb.t_i64
*/
#include <stdint.h>
#include <fcntl.h>
#include <stddef.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "build_callbacks.h"
#include "muapi.h"
// Global IDs
static MuID t_int;
static MuID t_i64;
static MuID t_long;
static MuID t_time_t;
static MuID t_timespec;
static MuID t_timespec_p;
static MuID t_timestamp_t;
void mubench_build_callback(MuIRBuilder *b)
{
t_int = b->gen_sym(b, "@clockcb.t_int");
t_i64 = b->gen_sym(b, "@clockcb.t_i64");
t_long = b->gen_sym(b, "@clockcb.t_long");
t_time_t = b->gen_sym(b, "@clockcb.t_time_t");
t_timespec = b->gen_sym(b, "@clockcb.t_timespec");
t_timespec_p = b->gen_sym(b, "@clockcb.t_timespec_p");
#ifdef __MACH__
t_timestamp_t = t_i64;
#else
t_timestamp_t = t_timespec;
#endif
b->new_type_struct(b, t_callback, IDARR(3, t_int, t_timestamp_t, t_timestamp_t), 3);
b->new_type_int(b, t_int, sizeof(int) * 8);
b->new_type_int(b, t_i64, 64);
b->new_type_int(b, t_long, sizeof(long) * 8);
b->new_type_int(b, t_time_t, sizeof(time_t) * 8);
b->new_type_struct(b, t_timespec, IDARR(2, t_time_t, t_long), 2);
b->new_type_uptr(b, t_timespec_p, t_timespec);
}
int mubench_get_callback_global_defs(MuID **parr)
{
MuID arr[] = {
t_int,
t_i64,
t_long,
t_time_t,
t_timespec,
t_timespec_p,
t_timestamp_t
};
*parr = (MuID *)malloc(sizeof(arr));
memcpy(*parr, arr, sizeof(arr));
return sizeof(arr) / sizeof(MuID);
}
\ No newline at end of file
/*
Copyright 2017 The Australian National University
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <stdint.h>
#include <fcntl.h>
#include <stddef.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "build_callbacks.h"
#include "muapi.h"
/**
* A dummy callback that does nohting.
.type @dummycb.cint = int<32>
.type @mubench.t_callback = struct<@mubench.t_ccharp>
.type @mubench.t_logargs = struct<@dummycb.cint>
.type @dummycb.csize_t = int<sizeof(size_t) * 8>
.type @dummycb.cssize_t = int<sizeof(ssize_t) * 8>
.funcsig @dummycb.sig_open = (@mubench.t_ccharp @dummycb.cint @dummycb.cint) -> (@dummycb.cint)
.funcsig @dummycb.sig_strlen = (@mubench.t_ccharp) -> (@dummycb.csize_t)
.funcsig @dummycb.sig_write = (@dummycb.cint @mubench.t_ccharp @dummycb.csize_t) -> (@dummycb.cssize_t)
.funcsig @dummycb.sig_close = (@dummycb.cint) -> (@dummycb.cint)
.type @dummycb.fp_open = ufuncptr<@dummycb.sig_open>
.type @dummycb.fp_strlen = ufuncptr<@dummycb.sig_strlen>
.type @dummycb.fp_write = ufuncptr<@dummycb.sig_write>
.type @dummycb.fp_close = ufuncptr<@dummycb.sig_close>
.const @dummycb.O_WRONLY <@dummycb.cint> = O_WRONLY
.const @dummycb.c_0666 <@dummycb.cint> = 0666
.const @dummycb.extern_open <@dummycb.fp_open> = EXTERN "open"
.const @dummycb.extern_strlen <@dummycb.fp_strlen> = EXTERN "strlen"
.const @dummycb.extern_write <@dummycb.fp_write> = EXTERN "write"
.const @dummycb.extern_close <@dummycb.fp_close> = EXTERN "close"
.funcdef @mubench.cb_init VERSION %v1 <@mubench.sig_cb_init> {
%blk0(<@mubench.t_ccharp> %param_s):
%cb = NEW <@mubench.t_callback>
%ircb = GETIREF <@mubench.t_callback_r> %cb
%irfld = GETFIELDIREF <@mubench.t_callback 0> %ircb
STORE <@mubench.t_ccharp> %irfld %param_s
RET %cb
}
.funcdef @dummycb.mubenc.cb_begin VERSION %v1 <@mubench.sig_cb_log> {
%blk0(<@mubench.t_callback_r> %cb <@mubench.t_logargs_r> %args):
RET
}
.funcdef @dummycb.mubenc.cb_end VERSION %v1 <@mubench.sig_cb_log> {
%blk0(<@mubench.t_callback_r> %cb <@mubench.t_logargs_r> %args):
RET
}
.funcdef @dummycb.mubenc.cb_report VERSION %v1 <@mubench.sig_cb_report> {
%blk0(<@mubench.t_callback_r> %cb <@mubench.t_ccharp> %filename):
%ircb = GETIREF <@mubench.t_callback_r> %cb
%irfld = GETFIELDIREF <@mubench.t_callback 0> %ircb
%param_s = LOAD <@mubench.t_ccharp> %irfld
%slen = CCALL <@dummycb.fp_strlen @dummycb.sig_strlen> @dummycb.extern_strlen (%param_s)
%fd = CCALL <@dummycb.fp_open @dummycb.sig_open> @dummycb.extern_open (%filename @dummycb.O_WRONLY @dummycb.c_0666)
%nbytes = CCALL <@dummycb.fp_write @dummycb.sig_write> @dummycb.extern_write (%fd %param_s %slen)
%res = CCALL @dummycb.<fp_close @dummycb.sig_close> @dummycb.extern_close (%fd)
RET
}
*/
static MuID cint, csize_t, cssize_t;
static MuID sig_open, sig_strlen, sig_write, sig_close;
static MuID fp_open, fp_strlen, fp_write, fp_close;
static MuID c_O_WRCREAT, c_0666, extern_open, extern_strlen, extern_write, extern_close;
void build_cb_init(MuIRBuilder *b);
void build_cb_begin(MuIRBuilder *b);
void build_cb_end(MuIRBuilder *b);
void build_cb_report(MuIRBuilder *b);
void mubench_build_callback(MuIRBuilder *b)
{
cint = b->gen_sym(b, "@dummycb.dummycb.cint");
csize_t = b->gen_sym(b, "@dummycb.dummycb.csize_t");
cssize_t = b->gen_sym(b, "@dummycb.dummycb.cssize_t");
sig_open = b->gen_sym(b, "@dummycb.dummycb.sig_open");
sig_strlen = b->gen_sym(b, "@dummycb.dummycb.sig_strlen");
sig_write = b->gen_sym(b, "@dummycb.dummycb.sig_write");
sig_close = b->gen_sym(b, "@dummycb.dummycb.sig_close");
fp_open = b->gen_sym(b, "@dummycb.dummycb.fp_open");
fp_strlen = b->gen_sym(b, "@dummycb.dummycb.fp_strlen");
fp_write = b->gen_sym(b, "@dummycb.dummycb.fp_write");
fp_close = b->gen_sym(b, "@dummycb.dummycb.fp_close");
c_O_WRCREAT = b->gen_sym(b, "@dummycb.dummycb.c_O_WRCREAT");
c_0666 = b->gen_sym(b, "@dummycb.dummycb.c_0666");
extern_open = b->gen_sym(b, "@dummycb.dummycb.extern_open");
extern_strlen = b->gen_sym(b, "@dummycb.dummycb.extern_strlen");
extern_write = b->gen_sym(b, "@dummycb.dummycb.extern_write");
extern_close = b->gen_sym(b, "@dummycb.dummycb.extern_close");
b->new_type_int(b, cint, 32);
b->new_type_struct(b, t_callback, IDARR(1, t_ccharp), 1);
b->new_type_struct(b, t_logargs, IDARR(1, cint), 1);
b->new_type_int(b, csize_t, sizeof(size_t) * 8);
b->new_type_int(b, cssize_t, sizeof(ssize_t) * 8);
b->new_funcsig(b, sig_open, IDARR(3, t_ccharp, cint, cint), 3, IDARR(1, cint), 1);
b->new_funcsig(b, sig_strlen, IDARR(1, t_ccharp), 1, IDARR(1, csize_t), 1);
b->new_funcsig(b, sig_write, IDARR(3, cint, t_ccharp, csize_t), 3, IDARR(1, cssize_t), 1);
b->new_funcsig(b, sig_close, IDARR(1, cint), 1, IDARR(1, cint), 1);
b->new_type_ufuncptr(b, fp_open, sig_open);
b->new_type_ufuncptr(b, fp_strlen, sig_strlen);
b->new_type_ufuncptr(b, fp_write, sig_write);
b->new_type_ufuncptr(b, fp_close, sig_close);
b->new_const_int(b, c_O_WRCREAT, cint, O_WRONLY | O_CREAT);
b->new_const_int(b, c_0666, cint, 0666);
b->new_const_extern(b, extern_open, fp_open, "open");
b->new_const_extern(b, extern_strlen, fp_strlen, "strlen");
b->new_const_extern(b, extern_write, fp_write, "write");
b->new_const_extern(b, extern_close, fp_close, "close");
build_cb_init(b);
build_cb_begin(b);
build_cb_end(b);
build_cb_report(b);
}