Skip to content

Commit

Permalink
Enable constructor/destructor on WASM
Browse files Browse the repository at this point in the history
Output `WASM_INIT_FUNCS` in linking custom section:

https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md#start-section

On WASM, no `.init_array` nor `.fini_array`,
call `__wasm_call_ctors` function on `_start` to call constructors.

Generate a function to call destructors
and register the function using `__cxa_atexit`.
  • Loading branch information
tyfkda committed Sep 14, 2024
1 parent 1b73957 commit 670952b
Show file tree
Hide file tree
Showing 14 changed files with 393 additions and 107 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ wcc-libs:
$(MAKE) CC=../wcc AR=llvm-ar -C libsrc wcc-libs

WCCLD_SRCS:=$(DEBUG_DIR)/wcc-ld.c $(WCC_DIR)/wasm_linker.c \
$(WCC_DIR)/wcc_util.c $(WCC_DIR)/emit_wasm.c $(WCC_DIR)/traverse.c $(WCC_DIR)/traverse_setjmp.c \
$(WCC_DIR)/wcc_util.c $(WCC_DIR)/emit_wasm.c $(WCC_DIR)/gen_wasm.c $(WCC_DIR)/traverse.c $(WCC_DIR)/traverse_setjmp.c \
$(wildcard $(CC1_FE_DIR)/*.c) \
$(UTIL_DIR)/util.c $(UTIL_DIR)/table.c $(UTIL_DIR)/archive.c
WCCLD_OBJS:=$(addprefix $(WCC_OBJ_DIR)/,$(notdir $(WCCLD_SRCS:.c=.o)))
Expand Down
22 changes: 4 additions & 18 deletions libsrc/_wasm/crt0/_start.c
Original file line number Diff line number Diff line change
@@ -1,24 +1,9 @@
#include "alloca.h" // alloca
#include "stdio.h" // fflush
#include "stdlib.h" // atexit, exit
#include "stdlib.h" // exit

#include "../../stdio/_fileman.h"
#include "../wasi.h"

extern FILEMAN __fileman;

inline void __flush_all_files(void) {
fflush(stdout);
fflush(stderr);

struct FILE **files = __fileman.opened;
for (int i = 0, length = __fileman.length; i < length; ++i)
fflush(files[i]);
}

static void _atexit_proc(void) {
__flush_all_files();
}
extern void __wasm_call_ctors(void);

int __max_preopen_fd = 3;

Expand Down Expand Up @@ -55,7 +40,8 @@ void _start(void) {

__max_preopen_fd = find_preopens();

atexit(_atexit_proc);
__wasm_call_ctors();

int ec = main(argc, argv);
exit(ec);
#undef main
Expand Down
39 changes: 39 additions & 0 deletions libsrc/stdlib/__cxa_atexit.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#include "stdlib.h"
#include "_exit.h"

#define MAX (8)

typedef struct {
void (*dtor)(void*);
void *arg;
void *dso_handle;
} CxaAtexitBuf;

static CxaAtexitBuf buf[MAX];
static int count;

void *__dso_handle;

int __cxa_atexit(void (*func)(void*), void *arg, void *d) {
if (count >= MAX)
return 1;
CxaAtexitBuf *p = &buf[count++];
p->dtor = func;
p->arg = arg;
p->dso_handle = d;
return 0;
}

static void call_cxa_atexit_funcs(void) {
for (CxaAtexitBuf *p = &buf[count]; p > buf; ) {
--p;
(*p->dtor)(p->arg);
}
}

__attribute__((constructor))
static void register_atexit(void) {
static OnExitChain chain = {NULL, call_cxa_atexit_funcs};
chain.next = __on_exit_chain;
__on_exit_chain = &chain;
}
6 changes: 6 additions & 0 deletions libsrc/stdlib/exit.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ OnExitChain *__on_exit_chain;
void exit(int code) {
// TODO: Guard multiple calls

#if defined(__WASM)
// On wcc, if there is a indirect function call but no actual function reference exists,
// table/elem section are not emitted and cause a load error.
// To avoid this, make sure a function reference exists and table/elem section are emitted.
(void)exit;
#endif
OnExitChain *chain = __on_exit_chain;
__on_exit_chain = NULL;
for (; chain != NULL; chain = chain->next) {
Expand Down
165 changes: 117 additions & 48 deletions src/cc/frontend/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -948,79 +948,148 @@ static Declaration *parse_declaration(void) {
return NULL;
}

#if XCC_TARGET_PLATFORM == XCC_PLATFORM_APPLE
static void generate_dtor_func(Vector *decls) {
const Name *destructor_name = alloc_name("destructor", NULL, false);
Vector *dtors = NULL;
for (int i = 0, len = decls->len; i < len; ++i) {
Declaration *decl = decls->data[i];
if (decl == NULL || decl->kind != DCL_DEFUN)
continue;
Function *func = decl->defun.func;
if (func->attributes != NULL) {
if (table_try_get(func->attributes, destructor_name, NULL)) {
if (dtors == NULL)
dtors = new_vector();
vec_push(dtors, func);
}
}
#if XCC_TARGET_PLATFORM == XCC_PLATFORM_APPLE || XCC_TARGET_PLATFORM == XCC_PLATFORM_WASI
static Function *generate_dtor_caller_func(Vector *dtors) {
// Generate function:
// void dtor_caller(void*) {
// dtor1();
// ...
// }

Vector *param_types = new_vector();
vec_push(param_types, &tyVoidPtr);
Type *functype = new_func_type(&tyVoid, param_types, false);

Vector *top_vars = new_vector();
var_add(top_vars, alloc_name("p", NULL, false), &tyVoidPtr, VS_PARAM);

const Token *functok = alloc_dummy_ident();
Table *attributes = NULL;
Function *func = define_func(functype, functok, top_vars, VS_STATIC, attributes);

assert(curfunc == NULL);
assert(is_global_scope(curscope));
curfunc = func;

func->scopes = new_vector();
Scope *scope = enter_scope(func);
scope->vars = top_vars;

// Construct function body: call destructors.
Vector *stmts = new_vector();
for (int i = 0; i < dtors->len; ++i) {
Function *dtor = dtors->data[i];
const Token *token = NULL;
Vector *args = new_vector();
Expr *func = new_expr_variable(dtor->name, dtor->type, token, global_scope);
Expr *call = new_expr_funcall(token, func, args);
vec_push(stmts, new_stmt_expr(call));
}
if (dtors == NULL)
return;

// Declare: extern void __dso_handle();
func->body_block = new_stmt_block(NULL, stmts, scope, NULL);

exit_scope();
curfunc = NULL;

return func;
}

static Function *generate_dtor_register_func(Function *dtor_caller_func) {
// Generate function:
// __attribute__((constructor))
// void dtor_register(void) {
// __cxa_atexit(dtor_caller, NULL, &__dso_handle);
// }

// Declare: extern void *__dso_handle;
const Name *dso_handle_name = alloc_name("__dso_handle", NULL, false);
scope_add(global_scope, dso_handle_name, &tyVoidPtr, VS_EXTERN);

// Declare: extern int __cxa_atexit(void*, void*, void*);
// Declare: extern int __cxa_atexit(void (*)(void*), void*, void*);
const Name *cxa_atexit_name = alloc_name("__cxa_atexit", NULL, false);
Type *cxa_atexit_functype = new_func_type(&tyVoid, NULL, false);
define_func(cxa_atexit_functype, alloc_ident(cxa_atexit_name, NULL, cxa_atexit_name->chars, NULL), new_vector(), VS_EXTERN, NULL);
Type *cxa_atexit_functype;
{
Vector *cxa_atexit_param_types = new_vector();
vec_push(cxa_atexit_param_types, &tyVoidPtr); // void (*func)(void*)
vec_push(cxa_atexit_param_types, &tyVoidPtr);
vec_push(cxa_atexit_param_types, &tyVoidPtr);

Vector *param_vars = new_vector();
var_add(param_vars, alloc_name("x", NULL, false), &tyVoidPtr, VS_PARAM);
var_add(param_vars, alloc_name("y", NULL, false), &tyVoidPtr, VS_PARAM);
var_add(param_vars, alloc_name("z", NULL, false), &tyVoidPtr, VS_PARAM);

cxa_atexit_functype = new_func_type(&tyInt, cxa_atexit_param_types, false);
define_func(cxa_atexit_functype, alloc_ident(cxa_atexit_name, NULL, cxa_atexit_name->chars, NULL), param_vars, VS_EXTERN, NULL);
}

// Generate function that calls destructors. It is marked as `constructor` to register thenm.
Vector *param_types = new_vector();
Type *functype = new_func_type(&tyVoid, param_types, false);
Vector *top_vars = new_vector();

Type *functype = new_func_type(&tyVoid, new_vector(), false);
const Token *functok = alloc_dummy_ident();
Table *attributes = alloc_table();
assert(attributes != NULL);
table_put(attributes, alloc_name("constructor", NULL, false), NULL);
Function *func = define_func(functype, functok, new_vector(), VS_STATIC, attributes);

Function *func = define_func(functype, functok, top_vars, VS_STATIC, attributes);
func->flag |= FUNCF_HAS_FUNCALL; // Make frame pointer saved on prologue.

assert(curfunc == NULL);
assert(is_global_scope(curscope));
curfunc = func;

Vector *top_vars = new_vector();
func->scopes = new_vector();
Scope *scope = enter_scope(func);
scope->vars = top_vars;

// Construct function body: call destructors.
Vector *cxa_atexit_param_types = new_vector();
vec_push(cxa_atexit_param_types, &tyVoidPtr); // void (*func)(void*)
vec_push(cxa_atexit_param_types, &tyVoidPtr);
vec_push(cxa_atexit_param_types, &tyVoidPtr);
Type *cxa_atexit_type = new_func_type(&tyInt, cxa_atexit_param_types, false);
Vector *stmts = new_vector();
for (int i = 0; i < dtors->len; ++i) {
Function *dtor = dtors->data[i];
// __cxa_atexit(func, NULL, &__dso_handle);
const Token *token = NULL;
Vector *args = new_vector();
vec_push(args, new_expr_variable(dtor->name, dtor->type, token, global_scope));
vec_push(args, new_expr_fixlit(&tyVoidPtr, token, 0));
vec_push(args, new_expr_unary(EX_REF, &tyVoidPtr, NULL, new_expr_variable(dso_handle_name, &tyVoidPtr, token, global_scope)));
Expr *func = new_expr_variable(cxa_atexit_name, cxa_atexit_type, token, global_scope);
Expr *call = new_expr_funcall(token, func, args);
vec_push(stmts, new_stmt_expr(call));
}
const Token *token = NULL;
Vector *args = new_vector();
vec_push(args, make_refer(token, new_expr_variable(dtor_caller_func->name, dtor_caller_func->type, token, global_scope)));
vec_push(args, new_expr_fixlit(&tyVoidPtr, token, 0));
vec_push(args, new_expr_unary(EX_REF, &tyVoidPtr, NULL, new_expr_variable(dso_handle_name, &tyVoidPtr, token, global_scope)));
// __cxa_atexit(dtor_caller, NULL, &__dso_handle);
vec_push(stmts, new_stmt_expr(
new_expr_funcall(token, new_expr_variable(cxa_atexit_name, cxa_atexit_functype, token, global_scope), args)));

func->body_block = new_stmt_block(NULL, stmts, scope, NULL);

exit_scope();
curfunc = NULL;

vec_push(decls, new_decl_defun(func));
return func;
}

static void modify_dtor_func(Vector *decls) {
const Name *destructor_name = alloc_name("destructor", NULL, false);
Vector *dtors = NULL;
for (int i = 0, len = decls->len; i < len; ++i) {
Declaration *decl = decls->data[i];
if (decl == NULL || decl->kind != DCL_DEFUN)
continue;
Function *func = decl->defun.func;
if (func->attributes != NULL) {
if (table_try_get(func->attributes, destructor_name, NULL)) {
const Type *type = func->type;
if (type->func.params == NULL || type->func.params->len > 0 || type->func.ret->kind != TY_VOID) {
const Token *token = func->body_block != NULL ? func->body_block->token : NULL;
parse_error(PE_NOFATAL, token, "destructor must have no parameters and return void");
} else {
if (dtors == NULL)
dtors = new_vector();
vec_push(dtors, func);
}
}
}
}
if (dtors == NULL)
return;

Function *caller_func = generate_dtor_caller_func(dtors);
vec_push(decls, new_decl_defun(caller_func));

Function *register_func = generate_dtor_register_func(caller_func);
vec_push(decls, new_decl_defun(register_func));
}
#endif

Expand All @@ -1033,7 +1102,7 @@ void parse(Vector *decls) {
vec_push(decls, decl);
}

#if XCC_TARGET_PLATFORM == XCC_PLATFORM_APPLE
generate_dtor_func(decls);
#if XCC_TARGET_PLATFORM == XCC_PLATFORM_APPLE || XCC_TARGET_PLATFORM == XCC_PLATFORM_WASI
modify_dtor_func(decls);
#endif
}
15 changes: 15 additions & 0 deletions src/wcc/emit_wasm.c
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,21 @@ static void emit_linking_section(EmitWasm *ew) {
data_close_chunk(&linking_section, -1);
}

if (init_funcs != NULL) {
data_push(&linking_section, LT_WASM_INIT_FUNCS); // subsec type
data_open_chunk(&linking_section); // Payload start.
data_uleb128(&linking_section, -1, init_funcs->len); // Count
for (int i = 0; i < init_funcs->len; ++i) {
Function *func = init_funcs->data[i];
FuncInfo *info;
info = table_get(&func_info_table, func->name);
assert(info != NULL);
data_uleb128(&linking_section, -1, 65535); // Priority
data_uleb128(&linking_section, -1, info->index); // Symbol index
}
data_close_chunk(&linking_section, -1);
}

if (linking_section.len > 0) {
data_close_chunk(&linking_section, -1);

Expand Down
14 changes: 10 additions & 4 deletions src/wcc/gen_wasm.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
#include "wasm.h"
#include "wasm_obj.h"

#define CODE (((FuncExtra*)curfunc->extra)->code)
#define CODE (curcodeds)

#define ADD_LEB128(x) data_leb128(CODE, -1, x)
#define ADD_ULEB128(x) data_uleb128(CODE, -1, x)
Expand All @@ -34,6 +34,8 @@

static void gen_lval(Expr *expr);

DataStorage *curcodeds;

void add_code(const unsigned char *buf, size_t size) {
data_append(CODE, buf, size);
}
Expand Down Expand Up @@ -1611,16 +1613,19 @@ static void gen_defun(Function *func) {
if (func->scopes == NULL) // Prototype definition
return;

curfunc = func;

DataStorage *code = malloc_or_die(sizeof(*code));
data_init(code);
data_open_chunk(code);

FuncExtra *extra = func->extra;
assert(extra != NULL);
extra->code = code;
func->extra = extra;
uint32_t frame_size = allocate_local_variables(func, code);

curfunc = func;
curcodeds = code;

// Prologue

const Type *functype = func->type;
Expand Down Expand Up @@ -1701,10 +1706,11 @@ static void gen_defun(Function *func) {
ADD_CODE(OP_END);

size_t before = code->len;
data_uleb128(code, 0, code->len); // Insert code size at the top.
data_close_chunk(code, -1);
extra->offset = code->len - before;

curfunc = NULL;
curcodeds = NULL;
assert(cur_depth == 0);
}

Expand Down
Loading

0 comments on commit 670952b

Please sign in to comment.