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
|
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use std::{
ffi::CStr,
marker::PhantomData,
os::raw::{c_char, c_int, c_void},
ptr::null_mut,
};
#[repr(C)]
#[derive(Debug)]
pub struct TCCState {
_unused: [u8; 0],
}
pub const TCC_OUTPUT_MEMORY: i32 = 1;
extern "C" {
pub fn tcc_new() -> *mut TCCState;
pub fn tcc_delete(s: *mut TCCState);
pub fn tcc_set_options(s: *mut TCCState, str: *const c_char);
pub fn tcc_compile_string(s: *mut TCCState, buf: *const c_char) -> c_int;
pub fn tcc_add_symbol(
s: *mut TCCState,
name: *const c_char,
val: *const c_void,
) -> c_int;
pub fn tcc_set_output_type(s: *mut TCCState, output_type: c_int) -> c_int;
pub fn tcc_relocate(s1: *mut TCCState, ptr: *mut c_void) -> c_int;
pub fn tcc_get_symbol(s: *mut TCCState, name: *const c_char) -> *mut c_void;
}
/// Compilation context.
pub struct Compiler {
inner: *mut TCCState,
_phantom: PhantomData<TCCState>,
pub bin: Option<Vec<u8>>,
}
impl Compiler {
pub fn new() -> Result<Self, ()> {
// SAFETY: There is one context per thread.
let inner = unsafe { tcc_new() };
if inner.is_null() {
Err(())
} else {
let ret =
// SAFETY: set output to memory.
unsafe { tcc_set_output_type(inner, TCC_OUTPUT_MEMORY as c_int) };
assert_eq!(ret, 0);
Ok(Self {
inner,
_phantom: PhantomData,
bin: None,
})
}
}
pub fn set_options(&mut self, option: &CStr) -> &mut Self {
// SAFETY: option is a null-terminated C string.
unsafe {
tcc_set_options(self.inner, option.as_ptr());
}
self
}
pub fn compile_string(&mut self, p: &CStr) -> Result<(), ()> {
// SAFETY: p is a null-terminated C string.
let ret = unsafe { tcc_compile_string(self.inner, p.as_ptr()) };
if ret == 0 {
Ok(())
} else {
Err(())
}
}
/// # Safety
/// Symbol need satisfy ABI requirement.
pub unsafe fn add_symbol(&mut self, sym: &CStr, val: *const c_void) {
// SAFETY: sym is a null-terminated C string.
let ret = tcc_add_symbol(self.inner, sym.as_ptr(), val);
assert_eq!(ret, 0);
}
pub fn relocate_and_get_symbol(
&mut self,
sym: &CStr,
) -> Result<*mut c_void, ()> {
// SAFETY: pass null ptr to get required length
let len = unsafe { tcc_relocate(self.inner, null_mut()) };
if len == -1 {
return Err(());
};
let mut bin = Vec::with_capacity(len as usize);
let ret =
// SAFETY: bin is allocated up to len.
unsafe { tcc_relocate(self.inner, bin.as_mut_ptr() as *mut c_void) };
if ret != 0 {
return Err(());
}
// SAFETY: if ret == 0, bin is initialized.
unsafe {
bin.set_len(len as usize);
}
self.bin = Some(bin);
// SAFETY: sym is a null-terminated C string.
let addr = unsafe { tcc_get_symbol(self.inner, sym.as_ptr()) };
Ok(addr)
}
}
impl Drop for Compiler {
fn drop(&mut self) {
// SAFETY: delete state from tcc_new()
unsafe { tcc_delete(self.inner) };
}
}
#[cfg(test)]
mod test {
use super::*;
use std::ffi::CString;
#[test]
fn test_compiler_jit() {
let p = CString::new(
r#"
#include <stdint.h>
int32_t add(int32_t a, int32_t b) {
return a + b;
}
"#
.as_bytes(),
)
.unwrap();
let sym = CString::new("add".as_bytes()).unwrap();
let mut ctx = Compiler::new().unwrap();
let ops = CString::new("-nostdlib").unwrap();
ctx.set_options(&ops);
assert!(ctx.compile_string(&p).is_ok());
ctx.relocate_and_get_symbol(&sym).unwrap();
}
}
|