summaryrefslogtreecommitdiff
path: root/foreign64.c
blob: 4156680918f744df3c09ddc006c2f9378357b834 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
/*
  Copyright (C) 2019, 2020 Leo Tenenbaum.
  This file is part of toc. toc is distributed under version 3 of the GNU General Public License, without any warranty whatsoever.
  You should have received a copy of the GNU General Public License along with toc. If not, see <https://www.gnu.org/licenses/>.
*/
#if SIZE_MAX != U64_MAX
#error "What's going on? The 64-bit #foreign file was included, but size_t isn't 64 bits!"
#endif

#ifdef __cplusplus
#define external extern "C"
#else
#define external extern
#endif

#ifdef _WIN64

external U64 win64_call(FnPtr fn, U64 *args, I64 nargs);
external float win64_callf(FnPtr fn, U64 *args, I64 nargs);
external double win64_calld(FnPtr fn, U64 *args, I64 nargs);

static inline U64 foreign_calli(FnPtr fn, U64 *args, I64 nargs, bool *is_float) {
	(void)is_float;
	return win64_call(fn, args, nargs);
}

static inline float foreign_callf(FnPtr fn, U64 *args, I64 nargs, bool *is_float) {
	(void)is_float;
	return win64_callf(fn, args, nargs);
}

static inline double foreign_calld(FnPtr fn, U64 *args, I64 nargs, bool *is_float) {
	(void)is_float;
	return win64_calld(fn, args, nargs);
}
#else
external U64 systemv64_call(FnPtr fn, U64 *args, I64 nargs, bool *is_float);
external float systemv64_callf(FnPtr fn, U64 *args, I64 nargs, bool *is_float);
external double systemv64_calld(FnPtr fn, U64 *args, I64 nargs, bool *is_float);
static inline U64 foreign_calli(FnPtr fn, U64 *args, I64 nargs, bool *is_float) {
	return systemv64_call(fn, args, nargs, is_float);
}
static inline float foreign_callf(FnPtr fn, U64 *args, I64 nargs, bool *is_float) {
	return systemv64_callf(fn, args, nargs, is_float);
}
static inline double foreign_calld(FnPtr fn, U64 *args, I64 nargs, bool *is_float) {
	return systemv64_calld(fn, args, nargs, is_float);
}
#endif

// disable strict aliasing warnings
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
#endif
static Status val_to_word(Value v, Type *t, Location where, U64 *w) {
	switch (t->kind) {
	case TYPE_BUILTIN:
		switch (t->builtin) {
		case BUILTIN_I8: *w = (U64)v.i8; break;
		case BUILTIN_I16: *w = (U64)v.i16; break;
		case BUILTIN_I32: *w = (U64)v.i32; break;
		case BUILTIN_U8: *w = (U64)v.u8; break;
		case BUILTIN_U16: *w = (U64)v.u16; break;
		case BUILTIN_U32: *w = (U64)v.u32; break;
		case BUILTIN_I64: *w = (U64)v.i64; break;
		case BUILTIN_U64: *w = v.u64; break;
		case BUILTIN_F32: *w = (U64)*(U32 *)&v.f32; break;
		case BUILTIN_F64: *w = *(U64 *)&v.f64; break;
		case BUILTIN_CHAR: *w = (U64)v.charv; break;
		case BUILTIN_BOOL: *w = (U64)v.boolv; break;
		case BUILTIN_TYPE:
		case BUILTIN_VARARGS:
		case BUILTIN_NMS:
		case BUILTIN_VOID:
			goto unsupported;
		}
		break;
	case TYPE_PTR:
		*w = (U64)v.ptr; break;
	default:
	unsupported: {
		// @TODO(eventually)
		char *s = type_to_str(t);
		err_print(where, "#foreign functions can't take arguments of type %s at compile time on Windows.", s);
		free(s);
		return false;
	}
	}
	return true;
}
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif

static Status foreign_call(ForeignFnManager *ffmgr, FnExpr *fn, Type *ret_type, Type *arg_types, size_t arg_types_stride, Value *args, size_t nargs, Location call_where, Value *ret) {
	possibly_static_assert(sizeof(double) == 8); // if either of these assertions fails, you'll need to use libffcall
	possibly_static_assert(sizeof(float) == 4);

//#define FOREIGN_DEBUGGING 1

#if FOREIGN_DEBUGGING
	printf("Foreign call: %s(", fn->foreign.name);
#endif

	FnPtr fn_ptr = foreign_get_fn_ptr(ffmgr, fn, call_where);
	if (!fn_ptr) return false;
	// @OPTIM: use alloca/_malloca if available
	U64 *words = err_malloc(nargs * sizeof *words);
	bool *is_float = err_malloc(nargs);
	U64 *word = words;
	char *type = (char *)arg_types;
	for (size_t i = 0; i < nargs; ++i) {
	#if FOREIGN_DEBUGGING
		if (i) printf(", ");
		fprint_val(stdout, args[i], (Type *)type);
	#endif
		if (!val_to_word(args[i], (Type *)type, call_where, word))
			return false;
		is_float[i] = type_is_float((Type *)type);
		type += arg_types_stride;
		++word;
	}
#if FOREIGN_DEBUGGING
	printf(") => ");
	fflush(stdout);
#endif
	int kind = 0; // 0=>integer, 1=>f32, 2=>f64
	switch (ret_type->kind) {
	case TYPE_BUILTIN:
		switch (ret_type->builtin) {
		case BUILTIN_I8:
		case BUILTIN_I16:
		case BUILTIN_I32:
		case BUILTIN_I64:
		case BUILTIN_U8:
		case BUILTIN_U16:
		case BUILTIN_U32:
		case BUILTIN_U64:
		case BUILTIN_BOOL:
		case BUILTIN_CHAR:
		case BUILTIN_VOID:
			break;
		case BUILTIN_F32:
			kind = 1;
			break;
		case BUILTIN_F64:
			kind = 2;
			break;
		default:
			goto unsupported;
		}
		break;
	case TYPE_PTR:
		break;
	default:
	unsupported: {
		char *s = type_to_str(ret_type);
		// @TODO(eventually)
		err_print(call_where, "You can't call functions which return type %s at compile time on Windows.", s);
		free(s);
		return false;
	}
	}
	
	switch (kind) {
	case 0: {
		U64 r = foreign_calli(fn_ptr, words, (I64)nargs, is_float);
		switch (ret_type->kind) {
		case TYPE_BUILTIN:
			switch (ret_type->builtin) {
			case BUILTIN_I8: ret->i8 = (I8)r; break;
			case BUILTIN_I16: ret->i16 = (I16)r; break;
			case BUILTIN_I32: ret->i32 = (I32)r; break;
			case BUILTIN_I64: ret->i64 = (I64)r; break;
			case BUILTIN_U8: ret->u8 = (U8)r; break;
			case BUILTIN_U16: ret->u16 = (U16)r; break;
			case BUILTIN_U32: ret->u32 = (U32)r; break;
			case BUILTIN_U64: ret->u64 = (U64)r; break;
			case BUILTIN_BOOL: ret->boolv = (bool)r; break;
			case BUILTIN_CHAR: ret->charv = (char)r; break;
			case BUILTIN_VOID: (void)r; break;
			default: assert(0); break;
			}
			break;
		case TYPE_PTR:
			ret->ptr = (void *)r;
			break;
		default: assert(0); break;
		}
	} break;
	case 1:
		ret->f32 = foreign_callf(fn_ptr, words, (I64)nargs, is_float);
		break;
	case 2:
		ret->f64 = foreign_calld(fn_ptr, words, (I64)nargs, is_float);
		break;
	}
#if FOREIGN_DEBUGGING
	fprint_val(stdout, *ret, ret_type);
#if 1
	printf(" (errno: %d)", errno);
#endif
	printf("\n");
#endif
	free(words);
	free(is_float);
	return true;
}