Skip to content

Commit 62af411

Browse files
committed
sapi/was: add option "--precompile"
1 parent 181175e commit 62af411

File tree

4 files changed

+355
-1
lines changed

4 files changed

+355
-1
lines changed

sapi/was/config.m4

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ if test "$PHP_WAS" != "no"; then
1212

1313
PHP_ADD_MAKEFILE_FRAGMENT($abs_srcdir/sapi/was/Makefile.frag,$abs_srcdir/sapi/was,sapi/was)
1414
SAPI_WAS_PATH=sapi/was/php
15-
PHP_SELECT_SAPI(was, program, was_main.c, "", '$(SAPI_WAS_PATH)')
15+
PHP_SELECT_SAPI(was, program, was_main.c precompile.cpp, "", '$(SAPI_WAS_PATH)')
1616
case $host_alias in
1717
*darwin*)
1818
BUILD_WAS="\$(CC) \$(CFLAGS_CLEAN) \$(EXTRA_CFLAGS) \$(EXTRA_LDFLAGS_PROGRAM) \$(LDFLAGS) \$(NATIVE_RPATHS) \$(PHP_GLOBAL_OBJS:.lo=.o) \$(PHP_BINARY_OBJS:.lo=.o) \$(PHP_WAS_OBJS:.lo=.o) \$(PHP_FRAMEWORKS) \$(EXTRA_LIBS) \$(ZEND_EXTRA_LIBS) -o \$(SAPI_WAS_PATH)"

sapi/was/precompile.cpp

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
/*
2+
+----------------------------------------------------------------------+
3+
| Copyright (c) CM4all GmbH |
4+
+----------------------------------------------------------------------+
5+
| This source file is subject to version 3.01 of the PHP license, |
6+
| that is bundled with this package in the file LICENSE, and is |
7+
| available at through the world-wide-web at the following url: |
8+
| http://www.php.net/license/3_01.txt. |
9+
| If you did not receive a copy of the PHP license and are unable to |
10+
| obtain it through the world-wide-web, please send a note to |
11+
| [email protected] so we can mail you a copy immediately. |
12+
+----------------------------------------------------------------------+
13+
| Author: Max Kellermann <[email protected]> |
14+
+----------------------------------------------------------------------+
15+
*/
16+
17+
#include "precompile.h"
18+
#include "php.h"
19+
#include "zend_system_id.h"
20+
#include "zend_exceptions.h"
21+
#include "../../ext/opcache/ZendAccelerator.h"
22+
#include "../../ext/opcache/zend_accelerator_util_funcs.h"
23+
#include "../../ext/opcache/zend_file_cache.h"
24+
#include "../../ext/opcache/zend_shared_alloc.h"
25+
#include "../../ext/opcache/zend_persist.h"
26+
#include "../../ext/opcache/ZipFormat.hxx"
27+
#include "ScopeExit.hxx"
28+
29+
#include <zlib.h> // for crc32()
30+
31+
#include <vector>
32+
33+
#include <fcntl.h>
34+
#include <malloc.h>
35+
#include <unistd.h>
36+
37+
static bool
38+
PrecompileFile(zend_file_handle *file_handle, int output_fd)
39+
{
40+
/* compile */
41+
42+
zend_op_array *op_array;
43+
zend_persistent_script *persistent_script =
44+
opcache_compile_file(file_handle, ZEND_INCLUDE, &op_array);
45+
if (persistent_script == nullptr) {
46+
if (EG(exception))
47+
zend_exception_error(EG(exception), E_ERROR);
48+
49+
return false;
50+
}
51+
52+
persistent_script->script.filename = file_handle->opened_path != nullptr
53+
? file_handle->opened_path
54+
: file_handle->filename;
55+
56+
/* optimize */
57+
58+
zend_optimize_script(&persistent_script->script, ZCG(accel_directives).optimization_level, ZCG(accel_directives).opt_debug_level);
59+
60+
/* persist */
61+
62+
zend_shared_alloc_init_xlat_table();
63+
64+
constexpr bool for_shm = false;
65+
66+
const size_t memory_used = zend_accel_script_persist_calc(persistent_script, for_shm);
67+
68+
// TODO align?
69+
void *mem = ZCG(mem) = memalign(64, memory_used);
70+
AtScopeExit(mem) { free(mem); };
71+
72+
zend_shared_alloc_clear_xlat_table();
73+
74+
/* Copy into memory block */
75+
zend_persistent_script *new_persistent_script = zend_accel_script_persist(persistent_script, for_shm);
76+
77+
zend_shared_alloc_destroy_xlat_table();
78+
79+
new_persistent_script->is_phar = false;
80+
81+
/* Consistency check */
82+
assert((char*)new_persistent_script->mem + new_persistent_script->size == (char*)ZCG(mem));
83+
84+
new_persistent_script->dynamic_members.checksum = zend_accel_script_checksum(new_persistent_script);
85+
86+
/* store */
87+
88+
return zend_file_cache_script_store_fd(output_fd,
89+
new_persistent_script,
90+
for_shm);
91+
}
92+
93+
static bool
94+
PrecompileFile(const char *source_filename, int output_fd)
95+
{
96+
zend_file_handle file_handle;
97+
zend_stream_init_filename(&file_handle, source_filename);
98+
AtScopeExit(&file_handle) { zend_destroy_file_handle(&file_handle); };
99+
100+
return PrecompileFile(&file_handle, output_fd);
101+
}
102+
103+
static bool
104+
CommitFile(int tmp_fd, int directory_fd, const char *new_path)
105+
{
106+
unlinkat(directory_fd, new_path, 0);
107+
108+
char fd_path[64];
109+
snprintf(fd_path, sizeof(fd_path), "/proc/self/fd/%d", tmp_fd);
110+
if (linkat(AT_FDCWD, fd_path,
111+
directory_fd, new_path,
112+
AT_SYMLINK_FOLLOW) < 0) {
113+
perror("Failed to commit file");
114+
return false;
115+
}
116+
117+
return true;
118+
}
119+
120+
[[gnu::pure]]
121+
static const char *
122+
NormalizeFilename(const char *filename) noexcept
123+
{
124+
if (strncmp(filename, "./", 2) == 0 && filename[2] != 0)
125+
filename += 2;
126+
127+
return filename;
128+
}
129+
130+
static ZipFileHeader
131+
WritePreliminaryZipFileHeader(int fd, const char *filename)
132+
{
133+
const size_t filename_length = strlen(filename);
134+
135+
ZipFileHeader h{};
136+
h.compression_method = 0;
137+
h.name_length = filename_length;
138+
write(fd, &h, sizeof(h));
139+
140+
write(fd, filename, filename_length);
141+
142+
return h;
143+
}
144+
145+
[[gnu::pure]]
146+
static uint32_t
147+
CrcFromFile(int fd, off_t offset, size_t size) noexcept
148+
{
149+
auto crc = crc32(0, nullptr, 0);
150+
151+
while (size > 0) {
152+
Bytef buffer[16384];
153+
auto nbytes = pread(fd, buffer, std::min(sizeof(buffer), size),
154+
offset);
155+
if (nbytes <= 0)
156+
abort();
157+
158+
crc = crc32(crc, buffer, nbytes);
159+
offset += nbytes;
160+
size -= nbytes;
161+
}
162+
163+
return crc;
164+
}
165+
166+
bool
167+
precompile(const char *const*files, size_t n_files)
168+
{
169+
CG(compiler_options) |= ZEND_COMPILE_WITHOUT_EXECUTION|ZEND_COMPILE_WITH_FILE_CACHE;
170+
171+
const int fd = open(".", O_TMPFILE|O_RDWR|O_CLOEXEC, 0644);
172+
if (fd < 0) {
173+
perror("Failed to create temporary file");
174+
return false;
175+
}
176+
177+
AtScopeExit(fd) { close(fd); };
178+
179+
std::vector<ZipDirectoryEntry> entries;
180+
entries.reserve(n_files);
181+
182+
/* compile all sources specified on the command line and
183+
append the opcode files into the ZIP file */
184+
for (size_t i = 0; i < n_files; ++i) {
185+
const char *const source_filename = NormalizeFilename(files[i]);
186+
187+
/* remember the offset and write a preliminary
188+
ZipFileHeader (which will be updated later) */
189+
const auto header_offset = lseek(fd, 0, SEEK_CUR);
190+
auto h = WritePreliminaryZipFileHeader(fd, source_filename);
191+
192+
/* compile the file and write the opcode into the ZIP
193+
file */
194+
const auto data_offset = lseek(fd, 0, SEEK_CUR);
195+
if (!PrecompileFile(source_filename, fd)) {
196+
fprintf(stderr, "Failed to compile %s\n", source_filename);
197+
return false;
198+
}
199+
200+
/* finalize the ZipFileHeader, updating the size and
201+
the CRC */
202+
203+
const auto end_offset = lseek(fd, 0, SEEK_CUR);
204+
const size_t data_size = end_offset - data_offset;
205+
206+
h.compressed_size = h.uncompressed_size = data_size;
207+
h.crc = CrcFromFile(fd, data_offset, data_size);
208+
209+
pwrite(fd, &h, sizeof(h), header_offset);
210+
211+
/* remember header data so we can later write central
212+
directory */
213+
214+
entries.emplace_back(h);
215+
entries.back().local_header_offset = header_offset;
216+
}
217+
218+
/* write the central directory */
219+
220+
ZipDirectoryEnd end{};
221+
end.total_entries = n_files;
222+
end.directory_offset = lseek(fd, 0, SEEK_CUR);
223+
224+
size_t directory_size = 0;
225+
for (size_t i = 0; i < n_files; ++i) {
226+
const char *const name = NormalizeFilename(files[i]);
227+
const size_t name_length = strlen(name);
228+
229+
const auto &entry = entries[i];
230+
write(fd, &entry, sizeof(entry));
231+
write(fd, name, name_length);
232+
233+
directory_size += sizeof(entry) + name_length;
234+
}
235+
236+
end.directory_size = directory_size;
237+
238+
write(fd, &end, sizeof(end));
239+
240+
/* truncate the ZIP file at the current position, just in case
241+
we reverted a file which was already written */
242+
ftruncate(fd, lseek(fd, 0, SEEK_CUR));
243+
244+
/* commit the ZIP file, link it to its final filename */
245+
246+
char zip_filename[64];
247+
sprintf(zip_filename, "opcache-%.32s.zip", zend_system_id);
248+
CommitFile(fd, AT_FDCWD, zip_filename);
249+
250+
return true;
251+
}

sapi/was/precompile.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
+----------------------------------------------------------------------+
3+
| Copyright (c) CM4all GmbH |
4+
+----------------------------------------------------------------------+
5+
| This source file is subject to version 3.01 of the PHP license, |
6+
| that is bundled with this package in the file LICENSE, and is |
7+
| available at through the world-wide-web at the following url: |
8+
| http://www.php.net/license/3_01.txt. |
9+
| If you did not receive a copy of the PHP license and are unable to |
10+
| obtain it through the world-wide-web, please send a note to |
11+
| [email protected] so we can mail you a copy immediately. |
12+
+----------------------------------------------------------------------+
13+
| Author: Max Kellermann <[email protected]> |
14+
+----------------------------------------------------------------------+
15+
*/
16+
17+
#pragma once
18+
19+
#include "zend_portability.h"
20+
21+
#include <stdbool.h>
22+
#include <stddef.h>
23+
24+
BEGIN_EXTERN_C()
25+
26+
bool
27+
precompile(const char *const*files, size_t n_files);
28+
29+
END_EXTERN_C()

0 commit comments

Comments
 (0)